Angular Listen Filtern mit Hash

Beim stöbern in SPA Anwendungsfällen bin ich der Idee verfallen, Listen anhand der Url bzw den enthaltenen Hash Wertes zu filtern. Eine Url setzt sich laut RFC 3986 wie folgt zusammen.

foo://example.com:8042/over/there?name=ferret#nose \_/ \______________/\_________/ \_________/ \__/ | | | | | scheme authority path query fragment | _____________________|__ / \ / \ urn:example:animal:ferret:nose

Der letzte Teil, das Fragment, wird beim HTTP Request vom Browser nicht zum Server übermittelt. Dies macht sich unter anderem Angular.js zu Nutze, um ein Client seitiges Routing der Views durchzuführen. Das ganze recht ähnlich zu ASP.NET MVC, nur eben nach dem # Zeichen, hier als Hash Key bezeichnet. So ist es möglich, die Ergebnismenge direkt per Link anzuspringen.

Im folgenden Beispiel, soll mit einer Art Navigationsleiste eine Filterung einer Liste durchgeführt werden. Um den Browser nicht zu überfordern, sollen jeweils AJAX Call Backs zum ASP.NET Web Api basierten Service durchgeführt werden, in dem der Hashkey als Parameter angegeben wird.

Der Service liefert Chatnachrichten anhand eines benannten Chatrooms. Das VB.NET Web Api Sample, decodiert die URL um z.B. Sonderzeichen korrekt zu behandeln.

   1:   Function Getchatmessage(room As String) As IQueryable(Of chatmessage)
   2:       Dim r = HttpUtility.UrlDecode(room)
   3:       Return db.chatmessage.Where(Function(c) c.room.roomname = r)
   4:   End Function

 

Folgendes wahnsinnig aufregendes UI, bietet dem Benutzer drei Optionen.

image

Die Links werden in der Form ASPX Seite in der FriendlyUrl Notation ohne Erweiterung, gefolgt vom # und Wert erzeugt.

image

Erste Lektion: Angular fügt nach dem Hash Zeichen einen Slash (Schrägstrich) in die URL im Browser ein, obwohl im HTML Source ohne.

image

Ich habe recherchiert dazu und herausgefunden, das es einen Hashbang Mode oder HMTL5 Mode gibt, Der genaue Grund ist noch ungeklärt. Später wird ein JavaScript Replace das wieder gerade biegen.

Um die HTML Inhalte zu erstellen, wurde eine Server rendering Methode gewählt. Hier sogar mit Webforms. Dies ist einfacher und erlaubt auch besseres Caching, also schnellere Ausführung.

Bereits vorbereitend wird eine Angular App samt dazu gehörigen Controller definiert. Die Anzeige des Hash per Databinding {{}} dient nur Kontrollzwecken. Das Listview Web Server Steuerelement lässt sich  per Model Binding an die Room Liste binden. Der VB.NET Codebehind Source Code findet sich im nächsten Listing.

Der Name des Rooms wird Url passenden Encodiert. Wenn der Benutzer diesen Link clickt, passiert faktisch nichts, außer das die URL im Browser um den Hash Wert ergänzt wird.

   1:  <body ng-app="App">
   2:      <form id="form1" runat="server">
   3:          <div ng-controller="chatController">
   4:              <div>Hash:{{target}}</div>
   5:              <div>
   6:                  <asp:ListView ID="roomlist" runat="server" 
ItemType="Concorde.room"
   7:                      SelectMethod="roomlist_GetData">
   8:                      <ItemTemplate>
   9:                          <a href='/chat#<%#HttpUtility.UrlEncode(Item.roomname)%>'>
  10:                              <%#Item.roomname%>
  11:                          </a>| 
  12:                  
  13:                      </ItemTemplate>
  14:                  </asp:ListView>

Inhalt der dazu gehörigen aspx.vb Datei. Es kommt ein Entity Framework Code First Model zum Einsatz,

   1:   Public Function roomlist_GetData() As IQueryable(Of room)
   2:          Dim db As New ConcordeContext
   3:          Return db.room
   4:   End Function

 

Kommt nun der spannende Teil, der Angular.js Controller. Dieser wird einen Callback per $http ausführen und dabei den Hash Wert aus der URL als Parameter übergeben. Dabei hilft $location, das per Path (eigentlich sollte es hash() sein) den passenden Wert aus der URL liefert. Nach Aufruf des REST Services stehen die JSON Daten in der Variable data (Zeile 20) und werden direkt der Chatmessages Eigenschaft im $Scope zugewiesen.

Das Besondere an diesem Angular Beispiel ist, das ein Watcher (Zeile 5) auf dem Viewmodell gesetzt wird, der die Änderung der URL als Event Geber nutzt und den Service Call initiiert. Ohne diesen, wird der Controller nur einmalig ausgeführt.

   1:  angular.module('App', [])
   2:    .controller('chatController',
function ($scope, $http,$location,$window) {
   3:      $scope.chatMessages = [];
   4:      $scope.location = $location;
   5:      $scope.$watch('location.absUrl()', function () {
   6:          $scope.target = $location.path(); 
   7:          $scope.ladeMessages();
   8:      }, true);
   9:      $scope.ladeMessages = function () {
  10:          if ($scope.target != null) { 
var para = $scope.target.replace('/', ''); };
  11:       
  12:          $http(
  13:       {
  14:           method: 'GET',
  15:           url: 'api/chatmessages',
  16:           params: {
  17:               room: para
  18:           }
  19:       })
  20:      .success(function (data, status) {
  21:          $scope.chatMessages =  data;
  22:      })
  23:      };
  24:   
  25:  });
  26:   

Fehlt noch der deklarative HTML5 Teil, der entweder eine Meldung für leere Daten oder die Liste anzeigt. Dies ist fast der einfachste Teil. Der Scope enthält die Liste der ChatMessages, die mit der ng-repeat Direktive durchlaufen wird. Ganz wie ein ASP.NET Repeater Control, nur eben Client Seitig.

   1:   <div ng-show="chatMessages.length==0">
   2:                keine Daten...
   3:   </div>
   4:    <div ng-repeat="msg in chatMessages">
   5:                      {{msg.Message}}
   6:   </div>

Windows Tipp - Wochentag in der Startleiste anzeigen

Selten mag man sich die Frage stellen, kann Windows auch den Wochentag in der Startleiste darstellen? Wenn jemand dies jedoch als Einstellung vorgenommen hat, finden es die meisten Benutzer gut und wollen wissen wie es geht, da es schon sehr praktisch ist.

So wird's gemacht

Die Einstellung vornehmen

Drücken Sie die Windows-Taste + R und geben "intl.cpl" ein, gefolgt von OK, danach wählen Sie Weitere Einstellungen… und den Reiter Datum.

clip_image001

Anpassung der Datumsdarstellung

Sie möchten, dass der Wochentag ausgeschrieben sein soll, dann geben Sie in das Feld Datum (kurz) statt "TT.MM.JJJ" "TTTT, TT.MM.JJJJ" ein.

clip_image002

Unterhalb der Felder stehen Erläuterungen zur Bedeutung der Buchstaben und deren Auswirkungen auf die Anzeige.

clip_image003

Bestätigen Sie die Einstellungen mit OK und der Wochentag erscheint wie gewünscht unten in der Startleiste.

clip_image005

Fertig

Windows 8.1 ohne Microsoft-Konto installieren

Wer ein lokales Benutzerkonto verwendet (damit keine Daten bei Microsoft gespeichert werden), wird beim Update auf Windows 8.1 während der Installation ein Microsoft-Konto anlegen müssen, damit es klappt. Es gibt dennoch eine Möglichkeit, die Registrierung zu umgehen, Sie müssen kein neues Konto, wie suggeriert, anlegen.

Wie Sie dabei vorgehen lesen Sie hier.

Update Starten:

Das Update auf Windows 8.1 ist im Windows-Store zu finden. Mit der „Windows-Taste“ gelangen Sie auf die Kacheloberfläche, klicken Sie hier auf Store, es erscheint die Kachel Kostenloses Update auf Windows 8.1.

image

Unter Umständen erscheint noch die Meldung, dass Sie Updates installieren müssen, was Sie entsprechend tun.

Die verborgene Einstellung auswählen:

Jetzt wird das Update heruntergeladen, der Computer startet neu, danach bestätigen Sie wie üblich die Lizenzbedingungen und klicken dann auf Express-Einstellungen verwenden.

Wenn „Melden Sie sich bei Ihrem Microsoft-Konto an“ erscheint, klicken Sie unterhalb auf die nahe unsichtbare Zeile Neues Konto erstellen und auf Mit einem vorhandenen Konto fortfahren.

image

Sie lassen alles Weitere durchlaufen und installieren.

Das ist alles, so klappt es!

Mehr Infos während Laufzeit oder Debug einer Web-Anwendung

Wer mit Visual Studio eine ASP.NET Anwendung schreibt, nutzt in der Regel den Debugger. Man kann aber auch Informationen über den Fluss in der Console ausgeben, sowohl server- als auch clientseitig.

Die Idee kam mir, während ich mit VB.NET Timer Code geschrieben habe und das Ticking beobachten wollte, ohne Breakpoints zu setzen. ASP.NET Servercode einfach mit

   1:   Debug.WriteLine("tick...")

Und schon tickt's im Visual Studio Output Window.

image

Mit JavaScript und diversen Frameworks wird das Thema noch schwieriger, weil Breakpoints manchmal in unüberschaubaren Code-Konstrukten enden. Auch hier kann man Log Messages ausgeben lassen und diese in den Developertools von Chrome oder IE mit F12 betrachten. Wer noch ein Alert verwendet, sollte schnellstens umstellen.

   1:    <script>
   2:          var myVar = setInterval(function () { myTimer() }, 1000);
   3:   
   4:          function myTimer() {
   5:              console.warn("tick..");
   6:              console.log("tick...");
   7:              
   8:          }
   9:      </script>

 

Im Internet Explorer 11:

image

dasselbe in Chrome:

image

Es existiert auch noch eine Methode Console.error mit dann rot gefärbter Ausgabe.

Auswertung zum StoragePool-Benchmark

Dies ist ein Update zu meinem vorherigen Beitrag über die Storage-Pools im Windows Server 2012 R2

http://blog.ppedv.de/post/2014/08/13/windows-server-2012-r2-storage-pools-mit-ssd-tiering.aspx

Ich habe noch ein paar weitere Tests bezüglich der Storage-Pools gemacht und diese in einer Tabelle ausgewertet:

Benchmark-Ergebnisse

 

Zusammenfassend kann man hier nun also sehen:

  • In den lesenden Disziplinen ist ein “echtes” RAID0 mittels RAID-Controller und den beiden SSDs am schnellsten
  • In den schreibenden Disziplinen ist der Storage-Pool mit einem Simple-Laufwerk am LSI-Controller am besten – dabei auch deutlich schneller als das controllereigene RAID0 von LSI und HP mit den SSDs
  • Der direkte Vergleich zwischen den Storage-Pools am HP-Controller und am LSI-Controller geht erstaunlicherweise zu Gunsten des LSI-Controllers aus und dies auch durchweg, obwohl der LSI-Controller (im Gegensatz zum HP-Controller) über keinen eigenen Cache verfügt (mit aktiviertem BBWC würde der HP-Controller wohl beim Schreiben noch deutlich besser werden)
  • Geht es um Datenredundanz (hier also RAID1 o.ä.), dann ist der Storage-Pool im Mirror-Modus mit der Kombination HDD plus SSD am besten!
  • Ein Storage-Pool mit 2 HDDs im Mirror-Mode ist fast genau so gut (oder genau so schlecht) wie die RAID1-Implementierung des HP- oder des LSI-Controllers

Microsoft Azure: Wartungsarbeiten in der Region Westeuropa

An diesem Wochenende führt Microsoft Wartungsarbeiten an der Netzwerkhardware in den Rechenzentren der Region Westeuropa durch. Dabei wird es zu Reboots der virtuellen Maschinen kommen, die zwischen 30 und 45 Minuten dauern können. Betroffen davon sind Instanzen von virtuellen Maschinen und Clouddiensten. Die Wartungsarbeiten finden am Freitag ab 20 Uhr und am Samstag ab 20 Uhr statt. An einem Tag sind die virtuellen Maschinen betroffen, während am nächsten Tag die Clouddienste betroffen sind.

Wie Microsoft am Freitag Abend bekannt gab, werden die Wartungsarbeiten für die Region Nordeuropa am kommenden Wochenende (22./23.08) zu den selben Zeiten durchgeführt.

CRUD mit Angular und ASP.NET Web API

Es gibt schon eine Reihe von Blogeinträgen, die sich mit Angular, dem Controller, Listen, Suche und dem Sortieren beschäftigen. Auch wie man einen REST Service erstellt, auf Basis von Web API, habe ich bereits beschrieben. Schon etwas länger liegt ein fertiges Create, Read, Update und Delete-Beispiel auf meiner Festplatte. Allerdings muss klar gesagt werden, dass dies mit hohen Risiken verbunden ist. Eine transparente API für z.B. das Löschen von Datensätzen wird irgendwann immer missbraucht. Schutz kann nur Verschlüsselung, Authentifizierung und ein zusätzliches Token-System bieten. Ganz generell sehe ich erhebliche Risken bei einem reinen SPA Ansatz. Shawn Wildermuth schreibt dazu “I dont believe in SPA”.

Trotzdem möchte ich rein akademisch ein End to End-Beispiel zeigen. Daten aus der Northwind SQL Server Datenbank werden über eine ASP.NET Web API als REST-Service zur Verfügung gestellt. Ein HTML5 basiertes UI lädt mit Hilfe von Angular die Daten und bindet sie dem MVx Pattern folgend. Ebenso werden die Events im Viewmodel erstellt und an Buttons gebunden.

Das UI könnte aus der Feder des Lada Tiger Designers stammen: es ist zweckmäßig schlicht. Im oberen Bereich kann ein neuer Customer erzeugt werden. Darunter findet sich eine Liste. Eine Reihe kann in einen Edit-Modus versetzt werden.

image

Zuerst wird ein Entity Model aus der Datenbank generiert und danach per Scaffolding ein neuer Web API Controller aus dem Modell. Schon mehrfach beschrieben. Der so erzeugte VB.NET Code bedarf keiner Änderungen.

   1:  Imports System.Data
   2:  Imports System.Data.Entity
   3:  Imports System.Data.Entity.Infrastructure
   4:  Imports System.Linq
   5:  Imports System.Net
   6:  Imports System.Net.Http
   7:  Imports System.Web.Http
   8:  Imports System.Web.Http.Description
   9:  Imports Angular1Learn
  10:  Imports System.Web.Http.OData
  11:   
  12:  Namespace Controllers
  13:      Public Class CustomersController
  14:          Inherits ApiController
  15:   
  16:          Private db As New NorthwindEntities
  17:   
  18:          ' GET: api/Customers
  19:   
  20:          Function GetCustomers() As IQueryable(Of Customers)
  21:              Return db.Customers
  22:          End Function
  23:   
  24:          ' GET: api/Customers/5
  25:          <ResponseType(GetType(Customers))>
  26:          Function GetCustomers(ByVal id As String) As IHttpActionResult
  27:              Dim customers As Customers = db.Customers.Find(id)
  28:              If IsNothing(customers) Then
  29:                  Return NotFound()
  30:              End If
  31:   
  32:              Return Ok(customers)
  33:          End Function
  34:   
  35:          ' PUT: api/Customers/5
  36:          <ResponseType(GetType(Void))>
  37:          Function PutCustomers(ByVal id As String, ByVal customers As Customers) As IHttpActionResult
  38:              If Not ModelState.IsValid Then
  39:                  Return BadRequest(ModelState)
  40:              End If
  41:   
  42:              If Not id = customers.CustomerID Then
  43:                  Return BadRequest()
  44:              End If
  45:   
  46:              db.Entry(customers).State = EntityState.Modified
  47:   
  48:              Try
  49:                  db.SaveChanges()
  50:              Catch ex As DbUpdateConcurrencyException
  51:                  If Not (CustomersExists(id)) Then
  52:                      Return NotFound()
  53:                  Else
  54:                      Throw
  55:                  End If
  56:              End Try
  57:   
  58:              Return StatusCode(HttpStatusCode.NoContent)
  59:          End Function
  60:   
  61:          ' POST: api/Customers
  62:          <ResponseType(GetType(Customers))>
  63:          Function PostCustomers(ByVal customers As Customers) As IHttpActionResult
  64:              If Not ModelState.IsValid Then
  65:                  Return BadRequest(ModelState)
  66:              End If
  67:   
  68:              db.Customers.Add(customers)
  69:   
  70:              Try
  71:                  db.SaveChanges()
  72:              Catch ex As DbUpdateException
  73:                  If (CustomersExists(customers.CustomerID)) Then
  74:                      Return Conflict()
  75:                  Else
  76:                      Throw
  77:                  End If
  78:              End Try
  79:   
  80:              Return CreatedAtRoute("DefaultApi", New With {.id = customers.CustomerID}, customers)
  81:          End Function
  82:   
  83:          ' DELETE: api/Customers/5
  84:          <ResponseType(GetType(Customers))>
  85:          Function DeleteCustomers(ByVal id As String) As IHttpActionResult
  86:              Dim customers As Customers = db.Customers.Find(id)
  87:              If IsNothing(customers) Then
  88:                  Return NotFound()
  89:              End If
  90:   
  91:              db.Customers.Remove(customers)
  92:              db.SaveChanges()
  93:   
  94:              Return Ok(customers)
  95:          End Function
  96:   
  97:          Protected Overrides Sub Dispose(ByVal disposing As Boolean)
  98:              If (disposing) Then
  99:                  db.Dispose()
 100:              End If
 101:              MyBase.Dispose(disposing)
 102:          End Sub
 103:   
 104:          Private Function CustomersExists(ByVal id As String) As Boolean
 105:              Return db.Customers.Count(Function(e) e.CustomerID = id) > 0
 106:          End Function
 107:      End Class
 108:  End Namespace

 

Mit dem $http Service, der per Dependency Injection in den Controller injiziert wird, wird ein GET auf die REST URL abgesetzt. Im Erfolgsfall reicht eine direkte Zuweisung der Rückgabe an das Customers Array.

Hier wird eine Sucess und eine Error-Methode verwendet. In der Angular-Dokumentation wird dies als Promise bezeichnet. Technisch sind das Objekte, die Callback Methoden besitzen, die asynchron aufgerufen werden.

   1:  var url = 'api/Customers/';
   2:  angular.module('kunden2App', [])
   3:  .controller('CRUDController', function ($scope, $http) {
   4:      $scope.customers = [];
   5:       $http.get(url).success(function (data) {
   6:          $scope.customers = data;
   7:      })
   8:      .error(function (data) {
   9:          $scope.error = "An Error has occured while loading posts! " + data.ExceptionMessage;
  10:      });
  11:  });
  12:   

Mit ähnlicher Taktik wird als nächstes ein Datensatz gelöscht, hier mit der ID des Customers als Parameter in der URL. Ebenso wird eine Success und Error-Methode angelegt.

Allerdings muss nach dem Löschvorgang der Datensatz auch aus dem lokalen Viewmodel im Browser entfernt werden, genauer gesagt, aus allen Browsern, die diese Liste aktuell anzeigen. Wir wollen aber beim Machbaren bleiben. Nach einigen Tests mit unterschiedlichen Varianten wird das ganz klassische JavaScript verwendet, um die Liste im Scope zu durchlaufen und die ID des Customers zu vergleichen. Ist sie ident, hat der dazugehörige Customer im Array nichts mehr zu suchen.

   1:   $scope.delcustomer = function () {
   2:          var currentCustomer = this.customer;
   3:          $http.delete(url + currentCustomer.CustomerID).success(function (data) {
   4:              alert("gelöscht")
   5:              //foreach faktor x langsamer http://jsperf.com/angular-foreach-vs-native-for-loop/3
   6:              for (i = $scope.customers.length - 1; i >= 0; i--){
   7:                  if ($scope.customers[i].CustomerID === currentCustomer.CustomerID) {
   8:                      $scope.customers.splice(i, 1);
   9:                      return false;
  10:                  }
  11:   
  12:              }
  13:            
  14:          }).error(function (data) {
  15:              $scope.error = "Fehler " + data.ExceptionMessage;
  16:          });
  17:      };

 

Ich bin kein Freund von multifunktionalen Grids im Browser, aber die Menschheit ist Excel gewohnt und möchte in der Liste direkt editieren können. Dazu muss aber ein bzw. mehrere INPUT-Elemente dynamisch ein bzw. ausgeblendet werden. Die DataGrid-Darstellung benötigt einen Edit Modus, der als Property dem ViewModel hinzugefügt wird, hier benannt als addMode. Das Gleiche gilt für die Anzeige des Add Modus.

Der REST Service erwartet ein HTTP PUT Kommando mit der CustomerID als Key und dem kompletten Customer Objekt als Daten. Nach erfolgreichem Speichern wird der Modus umgeschalten und damit alle Felder sozusagen in Labels dargestellt.

   1:  angular.module('kunden2App', [])
   2:  .controller('CRUDController', function ($scope, $http) {
   3:      $scope.customers = [];
   4:      $scope.addMode = false;
   5:      $scope.toggleEdit = function () {
   6:          this.customer.editMode = !this.customer.editMode;
   7:      };
   8:      $scope.toggleAdd = function () {
   9:          $scope.addMode = !$scope.addMode;
  10:      };
  11:   
  12:      $scope.save = function () {
  13:          var cust = this.customer;
  14:          $http.put(url + cust.CustomerID,cust).success(function (data) {
  15:              cust.editMode = false;
  16:          }).error(function (data) {
  17:              $scope.error = "Fehler " + data.ExceptionMessage;
  18:          });
  19:      };

 

Außerdem werden andere Buttons eingeblendet.

image

Das Neuanlegen eines Datensatzes funktioniert auf Angular-Seite fast identisch, lediglich ein HTTP-Post ohne explizite ID, aber mit dem kompletten Customer-Objekt, wird übermittelt.

   1:  $scope.add = function () {
   2:       $http.post(url, this.newcustomer).success(function (data) {
   3:              $scope.addMode = false;
   4:              $scope.customers.push(data);
   5:          }).error(function (data) {
   6:              $scope.error = "Fehler " + data.ExceptionMessage;
   7:          });
   8:      };

 

Am Ende muss dem $Scope Customer Array noch der Kunde hinzugefügt werden, um ihn auch sichtbar zu haben.

image

Fehlt lediglich noch der HTML5 View. Die Events werden per ng-click an das Viewmodell bzw. die passenden Methoden gebunden. Dabei wird das Customer-Objekt als Parameter übergeben. Mit ng-hide wird das Ein- bzw. Ausblenden von Teilen der UI bezüglich des Viewmodell-Status sichergestellt. Das alles klappt mit der Angular-Zwei-Wege-Datenbindung per ng-model Attribut oder {{}} sehr einfach,

   1:    <body ng-app="kunden2App">
   2:          <div ng-controller="CRUDController">
   3:              <h2>Customers</h2>
   4:              <strong>{{ error }}</strong>
   5:            
   6:              <p ng-hide="addMode"><a ng-click="toggleAdd()" href="">Neu</a></p>
   7:   
   8:              <form name="Form1" ng-show="addMode">
   9:                  Name<input type="text" ng-model="newcustomer.CompanyName" required />
  10:                  CustomerID<input type="text" ng-model="newcustomer.CustomerID" required maxlength="5"/>
  11:                  <br />
  12:   
  13:                  <input type="submit" value="Add" ng-click="add()" ng-disabled="!Form1.$valid" />
  14:                  <input type="button" value="Cancel" ng-click="toggleAdd()" />
  15:   
  16:              </form>
  17:              <hr />
  18:   
  19:             Name Suchen <input type="text" ng-model="search.CompanyName" />
  20:              <table >
  21:                  <tr>
  22:                      <th>#</th>
  23:                      <th> <a href="" ng-click="reverse=!reverse;order('CompanyName', reverse)">Name <img ng-src="img/down{{reverse}}.gif"&lt;/a></</th>
  24:                      <th></th>
  25:                  </tr>
  26:               
  27:                  </tr>
  28:                  <tr ng-repeat="customer in customers | filter:search | orderBy:'CustomerID':reverse" >
  29:                      <td><strong ng-hide="customer.editMode">{{ customer.CustomerID }}</strong></td>
  30:                      <td>
  31:                          <p ng-hide="customer.editMode">{{ customer.CompanyName }}</p>
  32:                          <p ng-show="customer.editMode">
  33:                              <input type="text" ng-model="customer.CompanyName" />
  34:                          </p>
  35:                      </td>
  36:                      <td></td>
  37:                      <td>
  38:                          <p ng-hide="customer.editMode">
  39:                              <a ng-click="toggleEdit(customer)" href="">Edit</a> |
  40:                              <a ng-click="delcustomer(customer)" href="">Delete</a>
  41:                          </p>
  42:                          <p ng-show="customer.editMode">
  43:                              <a ng-click="save(customer)" href="">Speichern</a> |
  44:                              <a ng-click="toggleEdit(customer)" href="">Cancel</a>
  45:                          </p>
  46:                      </td>
  47:                  </tr>
  48:              </table>
  49:    

Windows 7/8 Tipp – Ungebetene Gäste aussperren

Hat man als Benutzer den Verdacht, jemand versucht sich am eigenen Rechner anzumelden, gibt es für einen solchen Fall eine entsprechende Abhilfe. Leider erlaubt Windows beliebig viele Anmeldeversuche, potentielle Angreifer können es also problemlos mehrmals probieren. Mit der nachfolgend beschriebenen Anleitung sperren Sie die Anmeldung für 30 Minuten, wenn eine bestimmte Anzahl misslungener Versuche erreicht worden ist.

So öffnen Sie die Eingabeaufforderung:

  • in Windows 7 drücken Sie die Windows-Taste und tippen cmd ein, anschließend mit der rechten Maustaste auf cmd.exe, dann auf Als Administrator ausführen und Ja.
  • In Windows 8 drücken Sie die Windows-Taste + X, klicken auf Eingabeaufforderung (Administrator) und Ja.

Sperrung festlegen:

Hierzu geben Sie folgenden Befehl ein: net accounts /lockoutthreshold:3 und Enter

Hinweis: „3“ steht für die Anzahl falscher Anmeldungen

Windows meldet anschließend „Der Befehl wurde erfolgreich ausgeführt“. Ab jetzt wird der Rechner für 30 Minuten gesperrt, sofern die Eingabe drei Mal falsch erfolgt.

 

clip_image002

 

Sperrung aufheben:

Hierzu geben Sie folgenden Befehl ein: net accounts /lockoutthreshold:0

 

clip_image004

Das ist alles, fertig!

Viel Spaß damit

Windows Server 2012 R2: Storage Pools mit SSD-Tiering

Seit dem Windows Server 2012 beherrschen die Microsoft-Server-Systeme die Möglichkeit, so genannte “Storage Pools” (Speicherpools) zu bilden. Dabei handelt es sich um einen Zusammenschluss verschiedener lokal angebundener oder als LUN eingebundener Datenträger, z.B. via SAS, SATA oder auch USB. Bei den LUNs stehen iSCSI und FiberChannel zur Verfügung.

Die grundsätzliche Idee ist hier, eine Art Storage abzubilden, quasi eine “Storage-Virtualisierung”. Aus einem gebildeten Storage-Pool können anschließend logische Laufwerke, sogenannte “Speicherplätze” (Storage Spaces), gebildet werden. Dabei kann die Organisation des Speichers gewählt werden. Zur Verfügung stehen “Simple” (eine Art RAID0, keine Datensicherheit, aber eine Erhöhung des Durchsatzes durch die gleichzeitige Nutzung aller beteiligter Festplatten), “Mirror” (Eine Art RAID1 mit einfacher oder doppelter Spiegelung der Blöcke um ein Erhalten der Daten auch bei Ausfall einer Festplatte sicherzustellen) und “Parity” (Funktioniert wie RAID5; Paritätsdaten, die ein Wiederherstellen der Daten bei Ausfall einer Festplatte möglich machen, werden über alle beteiligten Datenträger verteilt). Dabei spielen Größe, Hersteller, Bus-Typ und Co. der Festplatten nahezu keine Rolle – ein Faktor, der die Storage Pools deutlich von klassischen RAID-Controllern unterscheidet.

spool7b2

Ein weiterer Faktor, der Storage-Pools von üblichen “lokalen” Speichertechnologien abgrenzt, ist die Möglichkeit, nachträglich Festplatten hinzuzufügen oder durch größere auszutauschen, um den zur Verfügung stehenden Speicherplatz zu erhöhen.

Seit dem zweiten Release des Server 2012 wird diese Technologie noch erweitert: Der Windows Server 2012 R2 beherrscht das sogenannte “SSD-Tiering” (Speicherebenen). Hierbei werden klassische HDDs und SSDs gemeinsam in einem Pool genutzt und die häufig gelesenen Daten auf die SSD verschoben, die seltener genutzten Daten bleiben auf der HDD. Alternativ kann man SSDs auch als Write-Back-Cache einsetzen.

spool6b

(Eine Anleitung, wie das genau eingerichtet werden kann, werde ich in Kürze ebenfalls veröffentlichen.)

Diese Möglichkeit – sowie Speicherpools insgesamt – sind in meinen Augen vor allem für kleinere und mittlere IT-Landschaften interessant, in denen keine “ausgewachsenen” SANs zum Einsatz kommen (können). Daher war für mich interessant, wie gut diese Technologie funktioniert und welche Geschwindigkeitsvorteile hierbei erzielt werden können. Daher habe ich diverse Test-Messungen unternommen, welche ich nun hier auswerten möchte. Zum Einsatz kam ein Fujitsu-Siemens-Server mit einem 4-Kern-Xeon-Prozessor und 8GB RAM, dazu 2 SATA-Festplatten von Western Digital und 2 SATA-SSDs von Crucial, jeweils mit etwa 250GB Speicher.

Zuerst habe ich die Festplatten einzeln getestet, um Vergleichswerte zu haben:

CrystalDiskMark_Pool_einzelne_HDD CrystalDiskMark_Pool_einzelne_SSD
hdtach_einzelne_HDD hdtach_einzelne_SSD

Hier ist natürlich zu erkennen, dass die SSD (rechts) deutlich schneller als die HDD (links) ist – und zwar in allen Disziplinen.

Weiterhin habe ich den Server-eigenen LSI RAID-Controller getestet (jeweils im RAID0, links 2x HDD, rechts 2x SSD):

CrystalDiskMark_2xHDD_RAID0_Controller CrystalDiskMark_2xSSD_RAID0_Controller
hdtach_2xHDD_RAID0_Controller hdtach_2xSSD_RAID0_Controller

Beim Lesen schafft der Controller sogar etwa doppelt so schnelle Werte – beim Schreiben brechen die Werte dafür dann recht ordentlich ein, der Betrieb ist also langsamer als eine einzelne Platte!

Und nun im direkten Vergleich ein Betrieb via Storage-Pool im “Simple”-Modus (links) und im “Mirror”-Modus (rechts):

CrystalDiskMark_Pool_mitSSD_Tearing_RAID0 CrystalDiskMark_Pool_mitSSD_Tearing_RAID1
hdtach_Pool_mitSSD_Tearing_RAID0 hdtach_Pool_mitSSD_Tearing_RAID1

Hier sieht man zunächst (CrystalDiskMark) etwa folgende Ergebnisse:

- Im Simple-Modus erreicht der Pool fast die Lese-Raten des RAID0 aus 2x SSD am Controller, übertrifft die Schreibraten aber deutlich

- Auch bei den wahlfreien Zugriffen sind durchweg bessere Werte zu sehen als beim RAID0 oder den einzelnen Platten

- Beim Mirror-Modus sind die Leseraten in etwa so wie beim Simple-Modus, beim Schreiben dafür aber nur etwa halb so hoch

- Insgesamt ist der Mirror-Modus aber besser als die beiden SSDs im RAID0 des Controllers

Wenn man nun aber noch HD Tach mit betrachtet, dann sieht man recht deutlich, dass die hohen Raten nur anfänglich erreicht werden (nämlich bis zu der Stelle, an der zwischen den SSDs und den HDDs gewechselt wird, hier etwa bei der Hälfte. Dennoch sind die Daten im “HDD-Bereich” recht gut!

Ich denke diese Ergebnisse zeigen, dass die Technologie zu hohen Geschwindigkeiten fähig ist, die selbst mit (zumindest einfacheren) RAID-Controllern nicht erreicht werden. Ich werde zeitnah noch eine tabellarische Gegenüberstellung der Werte ausarbeiten.

In diesem Beitrag habe ich weitere Tests und eine Auswertung in Tabellenform veröffentlicht: http://blog.ppedv.de/post/2014/08/15/Auswertung-zum-StoragePool-Benchmark.aspx  

HTML Input mit Bootstrap Edit Template umranden per Angular

Wer umfangreiche Formulare für den Webbrowser erstellt, wird sich bald wünschen weniger Code tippen zu müssen. Ein HTML Input wird mit Attributen versehen, einer Fehlermeldung, einem Label und einigen Klassen.

   1:  <div class="form-group">
   2:      <label class="col-xs-2 control-label" for="text1">Label</label>
   3:      <div class="col-xs-10">
   4:          <input type="text" id="text1" class="form-control" />
   5:          <span class="help-block">Fehlermeldung</span>
   6:      </div>
   7:  </div>

 

Tippen möchte ich aber nur etwas in der Form

 
   1:  <input type="text" id="text1" label="Name:" 
fehlermeldung="bitte füllen" required />

 

Der HTML Code außen rum soll sich automatisch daraus generieren. Ein Anwendungsfall für eine Angular.js Direktive. Das folgende Beispiel ist also der Prototyp zum Prototyp einer zukünftigen SPA Anwendung.

image

Dazu wird eine Angular App im HTML Code deklariert, samt zugehörigem Controller.

   1:  <body ng-app="App">
   2:      <div ng-controller="ExampleController">
   3:          <div formulargruppe data-label="Firmenname" 
data-fehlermeldung="Da muss was rein">
   4:              <input type="text" required name="feld1" 
ng-model="Customer.CompanyName"
   5:                      placeholder="Firma"/>
   6:          </div>
   7:      </div>

Wer sehr korrekt sein will, setzt ein data- vor die Eigenschaftsattribute wie fehlermeldung oder label. Es geht auch ohne. In Zeile 3 wird die Direktive Formulargruppe eingebunden. Auf keinen Fall in Direktiven einen Bindestrich im Namen verwenden, da dieser intern per Konvention entfernt wird.

Direktiven würde ich mit Behaviours aus XAML vergleichen. Damit lassen sich einem UI Element neue Eigenschaften, Visualisierung und Events zuordnen.

Das Ziel ist um jedes bestehende INPUT-Element eine Bootstrap-passende Formatierung zu legen und die Attribute aus dem INPUT weiter zu verwenden.

Die Direktive formulargruppe liefert mit einer Art Konstruktormethode Daten zurück. Mit restrict A ist die Nutzung nur als Attribut zulässig. Transclude ersetzt das bestehende DIV (Zeile 3 obiges Listing) mit dem Inhalt aus dem Template (Zeile 10 folgendes Listing). Das Input Element wandert dann an die Stelle ng-transclude, aus dem HTML Template.

Der besondere Gag ist die Datenbindung an einen lokalen Scope. Nicht zu verwechseln mit dem $Scope aus dem Viewmodel. Die Attribute label und Fehlermeldung und deren Werte aus der HTML-Seite (Zeile 3), werden durch de Zuweisung scrope: zu Eigenschaften. Diese können dann in Zeile 11 und 14 im Template frei gebunden werden. Das erinnert ein wenig an die ContentTemplates aus XAML und die Bindinglogik.

   1:     <script src="Scripts/angular.js"></script>
   2:      <script>
   3:          angular.module('App', [])
   4:           .directive('formulargruppe', function () {
   5:               return {
   6:                   restrict: 'A',
   7:                   transclude: true,
   8:                   replace: true,
   9:                   scope: { label: '@', fehlermeldung: '@' },
  10:                   template: '<div class="form-group">' +
  11:           '<label class="col-xs-2 control-label" for="text1">
{{label}}</label>' +
  12:           '<div class="col-xs-10">' +
  13:           ' <div ng-transclude ></div>' +
  14:           ' <span class="help-block">{{fehlermeldung}}</span> </div></div>',
  15:                              }
  16:   
  17:               }
  18:           })
  19:          .controller('ExampleController', ['$scope', function ($scope) {
  20:          ...
  21:          }]);

 

Das bleibt ein nicht unerhebliches Problem. Bootstrap braucht im Input-Element, für die runden Ecken, eine CSS Klasse form-control. Man könnte diese Klasse im Ausgangs-HTML-Code definieren, schöner wäre aber ein Automatismus über die Direktive. Allerdings ist im HTML Template kein INPUT vorhanden.

Genau für diesen Zweck, nämlich per Logik, das Template zu manipulieren, wurde die Link-Eigenschaft geschaffen. Im folgenden JavaScript Code wird das erste und einzige INPUT-Element selektiert. Dieses muss in einen Angular-Elementtyp umgewandelt werden, um die Jquery-Methode addClass nutzen zu können.

   1:  link: function (scope, element, attrs {
   2:          var input1 = element[0].querySelector("input");
   3:          angular.element(input1).addClass('form-control');

 

Letztendlich noch ein Blick auf den erzeugten HTML Code. Es fällt auf, dass ein zusätziches DIV um das INPUT Element gerendert wird. Erkennbar am Attribut ng-transclude. In meinen Tests mit allen möglichen Varianten Direktive als Element oder Attribut wurde auch ein  <span class="ng-scope">hannes</span> eingefügt.

   1:  <div ng-transclude="">
   2:            
   3:    <input name="feld1" class="ng-scope ng-pristine ng-untouched 
   4:  form-control ng-invalid ng-invalid-required" required=""
   5:   type="text" placeholder="Firma" 
   6:  ng-model="Customer.CompanyName">
   7:   
   8:  </div>

Dies ist nur der Anfang für ein vollständiges Angular-Formular mit Validierung und Bootstrap Design. Gezeigt wurde der Einsatz einer Direktive mit HTML Template und Binding. Mehr lernen Sie in meinem ppedv Angular Workshop auf der #ADCX.

Wissen aufbauen und Microsoft Surface Pro 3 kassieren

Month List