BigBasti's Blog About Me & my Digital Lifestyle

25Jan/124

ASP.NET MVC: HTML5 Elemente mit jQuery Fallback nutzen

Soo, nachdem ich die Überschrift mit all den hippen Begriffen vollgepackt habe die es in Sachen Web momentan so gibt möchte ich euch kurz erklären worum es in diesem Artikel gehen soll.

Ihr werdet sicher mitbekommen haben, dass HTML5 auf dem Vormarsch ist und auch wenn es immer noch nicht offiziell fertig ist findet man immer häufiger den HTML5-Header <!DOCTYPE html> auf diversen Seiten.

Eins der meiner Meinung nach nützlichsten Features die wir mit HTML5 erhalten sind die neuen Input-Typen. Denn bisher hatten wir keine Wahl und mussten type="text" benutzen!

Die Eingabefelder vom Typ Text konnten natürlich alles enthalten, sind aber nicht gerade Benutzerfreundlich wenn man etwas komplexere Daten eingeben soll wie zum Beispiel ein Datum.

In HTML5 wurden deswegen spezifische Typen für solche immer wieder einkehrende Eingaben eingeführt. Eine von ihnen ist &quot;Date&quot;:


Birthday: <input type="date" name="bday" />

Der große Vorteil hier ist, dass die Browser nun eine eigene Implementierung für diesen Typ von Eingabefeldern machen können. Falls der Browser nun also diesen neuen Eingabe Typ unterstützt blendet er automatisch einem (mehr oder weniger) schönen Date-Picker ein in dem man bequem das Datum wählen kann:

Ansicht des neuen Typen in verschiedenen Browsern

Ein weiterer großer Vorteil ist, dass auch die mobilen Browser wie z.B. auf dem iPhone diese neuen Elemente ebenfalls unterstützen und einen passenden Picker einblenden:

Date-Picker auf dem iPhone

Wie ihr in dem ersten Screenshot sehen könnt unterstützt der FireFox (zumindest zum Zeitpunkt des Screenshots) den neuen Typ nicht. In diesem Fall blendet er ein gewöhnliches Textfeld ein wo wir nun gezwungen sind das Datum auf die gewohnte (unbequeme) Weise einzutragen.

Übrigens, ich glaube ich muss euch nicht sagen warum der Internet Explorer hier nicht mit aufgeführt ist oder? ;-)

Und genau das ist das Problem um das es in diesem Blogpost geht. Wenn wir die tollen neuen Elemente nutzen gehen wir das Risiko ein, dass Nutzer die einen älteren Browser nutzen nicht in den Genuss einer bequemen Eingabe kommen.

jQuery Date-Picker

Wir könnten auch komplett auf das HTML5 Element verzichten und direkt alles mit jQuery machen, welches auch einen Date-Picker bietet, aber das ist auch nicht das gelbe vom Ei, denn dann wären die Mobilen Nutzer in Nachteil, da dort die Browser diese Elemente für gewöhnlich unterstützen und eine gewohnte Oberfläche für die Eingabe bieten.

Die Optimale Lösung ist offensichtlich eine Mischung beider Welten. Wenn der Benutzer die Seite mit einem kompatiblen Browser aufruft soll der Date-Picker des Browsers genutzt werden, wenn der Browser aber veraltet ist soll stattdessen der jQuery Fallback greifen und der JavaScript Date-Picker (siehe links) genutzt werden.

Zu unserem Glück macht uns ASP.NET MVC die Umsetzung dieses Plans sehr leicht, da es an den nötigen Stellen sehr einfach erweitert werden kann.

Schauen wir uns mal ein kleines Beispiel an. Wir haben eine Klasse Person, und wollen auch dessen Geburtstag speichern, hier wollen wir die tolle neue Funktionalität nutzen. Implementieren wir das Ganze jedoch erstmal wie gewohnt:


    public class Person
    {
        public int ID { get; set; }
        public String Name { get; set; }
        public DateTime Geburtstag { get; set; }
    }

Wenn wir die Model-Klasse haben können wir das MVC Scaffolding nutzen um für uns eine View zum Erstellen neuer Personen anzulegen:

MVC Scaffolding kommt uns zu Hilfe

Schauen wir uns nun doch mal an was da für uns tolles generiert wurde:


    <div class="editor-field">
        @Html.EditorFor(model => model.Geburtstag)
        @Html.ValidationMessageFor(model => model.Geburtstag)
    </div>

Auffällig hierbei ist, dass hier @Html.EditorFor genutzt wird und nicht etwa @Html.TextBoxFor! Der Hintergrund ist der, dass MVC bei der Generierung versuchen wird ein passendes Element für den angegebenen Datentyp (DateTime) zu bestimmen, aber da MVC standardmäßig nur den Typ Text kennt wird hier eigentlich immer ein Input-Element vom Typ text generiert.

Hier kommt uns die Erweiterbarkeit von MVC zu gute, denn wir können ganz einfach NuGet nutzen um uns die nötige Funktionalität zu verschaffen. Wir installieren das Packet MvcHtml5Templates.

Installation über NuGet

Neue Templates

Nachdem NuGet alles erledigt hat werdet ihr feststellen, dass ihr ein paar neue Dateien in eurem Views/Shared Verzeichnis habt (siehe Bild links). Und wie ihr schon an dem Namen erkennen könnt sind das die neuen Input-Typen aus HTML5.

Diese Templates machen im Grunde nichts anderes als die MVC-Eigenen zu überschreiben und versehen die mit dem passendem Type-Attribut.

Hier sind euch keine Grenzen gesetzt ihr könnt natürlich auch eire eigenen Templates definieren mit euren eigenen Typen. (Auch wenn man das eher selten benötigt)

Das Tolle: mehr müssen wir nicht machen. MVC wird nun zur Laufzeit statt einem Text ein Date Input-Element für uns anlegen. Beachtet, dass das nur geht wenn wir in der View die allgemeine Funktion @Html.EditorFor nutzen und keinen Spezifischen Typ angeben.

Schauen wir mal in den Sourcecode der zur Laufzeit für das Geburtstagsfeld generiert wird:


<input class="text-box single-line"
data-val="true"
data-val-required="Das Feld 'Geburtstag' ist erforderlich"
id="Geburtstag"
name="Geburtstag"
type="datetime"
value=""; />

Wie ihr seht wird nun der Korrekte Typ, nämlich datetime verwendet. Wenn wir die Seite nun also mit einem kompatiblen Browser aufrufen können wir ganz bequem das Datum wählen.

Leider ist der Opera Browser momentan wohl der einzige Desktop Browser der uns hier einen Benutzerfreundlichen Dialog einblendet (siehe erstes Bild oben), somit müssen wir dafür sorgen, dass die Benutzer mit anderen oder alten Browsern nicht benachteiligt werden.

JavaScript Magie

Auch für diese Problematik bringt MVC bereits alles mit was nötig ist sie zu lösen. Werfen wir doch mal einen Blick in unseren Scripts Ordner finden wir alle nötigen jQuery und jQuery UI Skripte, dazu kommt noch die modernizr Bibliothek die uns auch zu Gute kommen wird.

Die jQuery Bibliotheken liefern und die nötige Funktionalität die wir benötigen um diesen hübschen Date-Picker einzublenden.

Die modernizr Bibliothek dagegen hilft uns herauszufinden ob der Browser, den der Benutzer momentan verwendet die gewünschten HTML5 Features unterstützt.

Nun müssen wir also bei unseren Formularen prüfen, ob der Browser die nötigen Funktionen kennt und bei Bedarf die jQuery Klassen einbinden. Das Ganze ist dank der Einfachheit von Modernizr und jQuery ziemlich simpel.

Wechseln wir zu unserer View und fügen die Referenzen auf die nötigen Skripte und CSS Dateien ein:


<script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.js")" type="text/javascript"></script>
<link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />

Unten in der View fügen wir nun eine Modernizr Abfrage ein und prüfen, ob der Browser das DateTime-Feld unterstützt, und wenn nicht lassen wir jQuery das Ganze für uns regeln:


<script type="text/javascript">
    $(function () {
        if (!Modernizr.inputtypes.date) {
            $("input[type='datetime']").datepicker();
        }
    });
</script>

Mit dem Befehl $("input[type='datetime']").datepicker(); werden alle Input-Felder vom Typ DateTime durch den jQuery Picker ersetzt. Coole Sache!

Unser Browser kennt den Typ nicht :(

Unser Browser kennt den Typ nicht :(

Kleiner Hinweis am Rande: Das ist nicht wirklich best practice da die Skripte auch geladen werden wenn sie gar nicht benötigt werden (der Browser kennt das HTML5 Feld) ich habe das hier der Einfachheit geopfert.

Und das wars auch schon! Alle Browser die das Feld unterstützen blenden nun ihren Picker ein, ansonsten wird der jQuery Fallback genutzt.

Wir müssen uns auch nicht mit irgendwelchen UserAgent-Prüfungen herumschlagen und lassen das alles Modernizr erledigen, der das intern über JavaScript prüft und somit immer aktuell ist. Das heißt dass wenn irgendwann der IE plötzlich die neuen Typen kennt werden diese auch funktionieren!

Genauso könnt ihr auch bei all den anderen neuen HTML5 Typen vorgehen und euren Usern ein bestmögliches Bedienerlebnis bereiten ;-)

kick it on dotnet-kicks.de

17Jan/122

Konsolenausgaben in einer WindowsForms Applikation

Manchmal will man eine WindowsForms Anwendung auch über die Konsole aufrufen und dann möchte man natürlich auch Ausgaben der Anwendung in dem Konsolenfesnter sehen. Doch leider ist das nicht so einfach, da alle Aufrufe über die Console-Klasse werden ignoriert.

Doch warum ist das so? Das ist ganz einfach und liegt daran, dass das Konsolenfenster, das unser Programm startet zu einem anderem Prozess gehört, nämlich cmd.exe und somit nicht verbunden ist.

Was wir also tun müssen ist, unsere Ausgabe an das Konsolenfenster zu heften, das unsere Anwendung gestartet hat. Dafür kann man die Win32-Methode AttachConsole nutzen.

Diese ist sehr einfach aufgebaut und sollte vor der ersten Ausgabe an die Konsole aufgerufen werden:

Definition des Win32-Aufrufs: (Beispiele in VB.NET)


    <DllImport("kernel32.dll")> _
    Private Shared Function AttachConsole(dwProcessId As Integer) As Boolean
    End Function

Um die Ausgabe an den Prozess zu geben der uns aufgerufen hat (Parent-Process) kann man hier als Wert -1 übergeben:


    Private Const ATTACH_PARENT_PROCESS As Integer = -1

Was nun noch bleibt ist es die Methode aufzurufen, zum Beispiel in der Main-Methode der Applikation:


    Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        AttachConsole(ATTACH_PARENT_PROCESS)
        //[...]

Das wars auch schon, nun werden eure Ausgaben an das Konsolenfester umgeleitet. Um die Konsole wieder von eurem Prozess zu lösen kann die paremeterlose Methode FreeConsole genutzt werden.

Es lohnt sich hier natürlich dann auch eine Unterscheidung zu machen wie die App gestartet wurde, also ob über Konsole oder über Doppelklick wie immer. Wenn sie über die Konsole gestartet wird, sollte man vielleicht die Oberfläche nicht mehr anzeigen. Das kann man dann zB. daran erkennen, dass Environment.GetCommandLineArgs.Length > 1 ist.

Probleme

Leider gibts ein paar Nebeneffekte die nicht unbeachtlich sind.

Ein Problem ist, dass Konsolenfuntionen die die Ausgabe von Programmen umleiten zB. so: "myapp.exe -test >test.txt" nicht funktionieren, die Ausgabe erfolgt weiterhin auf der Konsole. Hierfür konnte ich leider keine Lösung finden, wenn euch was einfällt einfach kommentieren.

Das zweite ist nur eine Schönheitssache, nämlich wenn das Programm beendet wird wird erst nicht die gewohnte Konsoleneingabezeile angezeigt, sondern nur der blinkende Cursor, so dass es aussieht als ob das Programm noch laufen würde, was es aber nicht mehr tut. (Wie gesagt Schönheitssache) Das kann man zB. dadurch umgehen, dass man vor dem Beenden des Programms noch ein [Enter] auf der Konsole ausgibt.

Weitere Infos und auch eine kleine Diskussion gibts hier und hier.

5Jan/120

ASP.NET MVC: Zugriff auf Dateien einschraenken

Oft kommt es vor dass man bestimmte Inhalte nicht der breiten Öffentlichkeit präsentieren möchte. So will man beispielsweise bestimmte Inhalte nur autorisierten Benutzern oder nur Admins zur Verfügung stellen, andere User die nicht eingeloggt sind sollen diese Inhalte nicht abrufen können. Ein weiterer Anwendungsfall wäre auch hotlinking zu verhindern.

Dieses Verhalten kann man in ASP.NET MVC über verschiedene Wege erreichen. Eine beliebte Möglichkeit ist es

die Dateien über einen Controller zu verteilen.

Hierbei erstellt man einen Controller der eine Action hat in der die gewünschte Datei geladen und an den Browser zurückgegeben wird. Hier hat man dann die Bequemlichkeit, dass man problemlos prüfen kann ob der User eingeloggt ist und über die nötigen Rechte für das Bild verfügt. Das geht zum Beispiel mit dem Authorized-Attribut für die Actions.

Implementiert würde so eine Action dann wohl ca so aussehen:


public ActionResult Image(string id)
{
    var dir = Server.MapPath("/Private/Images");
    var path = Path.Combine(dir, id + ".jpg");
    return base.File(path, "image/jpeg");
}

Das Bild kann man dann so abrufen:


<img src="@Html.Content("~/Content/Image/" + model.pictureID)" />

Dieser Ansatz funktioniert problemlos hat aber den kleinen Nachteil, dass er nicht so performant ist wie der direkte Zugriff auf die Ressource.

Eine weitere (und auch meine bevorzugte) Möglichkeit eine solche Zugriffskontrolle zu implementieren ist es durch

einen eigenen RouteHandler.

Eine Beispielimplementierung wie man auf diese Weise Hotlinking verhindern kann habe ich in Mike Brinds Blog gefunden.

Hier sind im Grunde drei kleine Schritte nötig um einen eigenen RouteHandler zum Laufen zu bekommen. (Code von Mikes Blog)

Zunächst müssen wir die RouteHandler Klasse anlegen, die das IRouteHandler Interface und dessen einzige Methode GetHttpHandler implementiert:


public class ImageRouteHandler : IRouteHandler
{
  public IHttpHandler GetHttpHandler(RequestContext requestContext)
  {
    return new ImageHandler(requestContext);
  }
}

Als nächstes erstellen wir die oben genutzte ImageHandler Klasse in der wir das das IHttpHandler Interface implementieren:


public class ImageHandler : IHttpHandler
{
  public ImageHandler(RequestContext context)
  {
    ProcessRequest(context);
  }

  private static void ProcessRequest(RequestContext requestContext)
  {
    var response = requestContext.HttpContext.Response;
    var request = requestContext.HttpContext.Request;
    var server = requestContext.HttpContext.Server;

    var validRequestFile = requestContext.RouteData.Values["filename"].ToString();
    const string invalidRequestFile = "thief.gif";
    var path = server.MapPath("~/graphics/");

    response.Clear();
    response.ContentType = GetContentType(request.Url.ToString());

    if (request.ServerVariables["HTTP_REFERER"] != null &&
        request.ServerVariables["HTTP_REFERER"].Contains("yourdomain.com"))
    {
      response.TransmitFile(path + validRequestFile);
    }
    else
    {
      response.TransmitFile(path + invalidRequestFile);
    }
    response.End();
  }

  private static string GetContentType(string url)
  {
    switch (Path.GetExtension(url))
    {
      case ".gif":
        return "Image/gif";
      case ".jpg":
        return "Image/jpeg";
      case ".png":
        return "Image/png";
      default:
        break;
    }
    return null;
  }

  public bool IsReusable
  {
    get { return false; }
  }
}

Zu Schluss müssen wir diesen neuen Handler mit einer Route in der Global.asax registrieren:


routes.Add("ImagesRoute",
                 new Route("images/{filename}", new ImageRouteHandler()));

Hierbei ist noch wichtig, dass ihr die Route an der passenden Stelle registriert, sodass diese nicht ungewollt von einer anderen Route blockiert wird weil diese auch auf dieses Pattern matcht.

Nun werden alle Requests die auf die Route "images/{filename}" gehen und von einer anderen Domain stammen als eurer eigenen auf eine andere Grafik umgeleitet.

Nun bleibt noch ein kleines Problem, denn wenn das Verzeichnis mit den Bildern in eurem Content Verzeichnis liegt kann immer noch darauf verlinkt werden wenn man den vollen Pfad nimmt, also statt dem virtuellem Pfad

http://localhost/images/myimg.jpg

die URL benutzt unter der tatsächlich alle Bilder liegen:

http://localhost/Content/graphics/myimg.jpg

Dies liegt daran, dass der neue RouteHandler in diesem Fall nicht greift und die Default Route genutzt wird. Am einfachsten kann man dies verhindern wenn man den Zugriff für Unbefugte über die web.config sperrt:


  <location path="Content/graphics">
    <system.web>
      <authorization>
        <allow roles="admin" />
        <deny users="*" />
      </authorization>
    </system.web>
  </location>

Das war dann auch schon, ich nutze gern den zweiten Weg, da dieser mir einfacher und sauberer erscheint. Wenn euch noch andere Möglichkeiten einfallen wie man das anstellen kann dann immer her damit!

13Dez/110

SSL Teil 4: Serverseitige Authentifizierung mit Java

Nachdem wir in den letzen Teilen mehr die Theorie durchgenommen haben, möchte ich jetzt zu dem praktischen Teil kommen und euch zeigen wie man diese Theorie in Code umsetzen kann am Beispiel Java. (.NET wird auch noch folgen)

Wir wollen einfach einsteigen und implementieren erst mal nur die eine Serverseitige Authentifizierung, also muss der Server sich uns gegenüber ausweisen und wir sind in der Pflicht sein Zertifikat anzunehmen oder abzulehnen.

Wie in Teil 1 erklärt benötigen wir also zunächst einen Ort wo wir die Zertifikate sichern, denen wir vertrauen. Hier können wir (da wir in Java unterwegs sind) nicht die Windows-eigenen nehmen sondern müssen die so genannten Keystores von Java Nutzen.

Ein Keystore, wie der Name schon sagt ist nichts anderes als ein Speicher für Zertifikate. Wenn ihr Java installiert dann legt Java bereits einen Keystore für euch an, diesen findet ihr dann in eurem Java Verzeichnis Java\jre6\lib\security\cacerts die Datei „cacerts“ enthält hier bereits ähnlich wie bei Windows bereits die CA-Zertifikate von den großen Zertifizierungsstellen.

Es wir desweiterhin in Java unterschieden zwischen  Keystore und Truststore, beides sind im Grunde Keystores und unterscheiden sich nur im Namen und in ihrem Inhalt. Eine einzige Datei kann auch beide beinhalten, ist aber eher unüblich, da man die eigenen (privaten) Zertifikate von den CA-Zertifikaten separieren will.

Zuständigkeiten

So enthalten die Keystores für gewöhnlich die eigenen Privaten Schlüssel und die eigenen von anderen CAs signierte Zertifikate, die bei Verbindungen als Serverzertifikate (Public Keys) genutzt werden. Der Truststore ist das Gegenstück und enthält die Zertifikate denen man vertraut. So müssen wir die Zertifikate die wir vom Server beim Verbindungsaufbau erhalten gegen einen Truststore prüfen.

Fangen wir mit unserem Code an, wir wollen einen Client erstellen, der eine Socket-basierte Verbindung zu einem Server über SSL aufbaut und das vom Server zurückgegebene Zertifikat prüft.

Java ist auf diesem Gebiet sehr entgegenkommend und bietet für fast alle Anwendungsfälle fertige APIs und Klassen die uns fast die ganze Arbeit abnehmen.

Um den Prozess besser aufzeigen zu können schreiben wir uns eine Eigene SSLSocketFactory die für uns den Aufbau der Verbindung übernimmt und unserem Programm im Prinzip ein fertiges Socket liefert.

Dazu erstellen wir eine neue Klasse und lassen diese von SSLSocketFactory erben. Da das eine abstrakte Klasse ist müssen eir einige Methoden implementieren. Wenn das erledigt ist sieht unsere Klasse schon so aus:


/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    @Override
    public String[] getDefaultCipherSuites() {

    }

    @Override
    public String[] getSupportedCipherSuites() {

    }

    @Override
    public Socket createSocket(Socket socket, String string,
            int i, boolean bln) throws IOException {

    }

    @Override
    public Socket createSocket(String destinationAddress,
            int destinationPort) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(String string, int i,
            InetAddress ia, int i1) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
            InetAddress ia1, int i1) throws IOException {

    }
}

Nun müssen wir diesen Methoden Leben einhauchen, aber keine Angst das ist nicht so schlimm wie es aussieht, denn die einzige Änderung die wir machen müssen ist es der SSLSocktFactory beizubringen mit unserem Truststore zu arbeiten, der Rest der Funktionalität soll unberührt bleiben.

Fügen wir also einen Konstruktor zu unserer Klasse hinzu der das Initialisieren unserer Internen SSLSocketFactory mit unserem Truststore übernimmt.


/** Delegate {@link SSLSocketFactory}. */
private transient SSLSocketFactory factory;

private File caFile;
private KeyStore ks;

private SSLContext context;
private TrustManagerFactory tmf;
private X509TrustManager defTrustManager;
private SavingTrustManager tm;

/**
 * Neue Truststore-basierte SSL-SocketFactory
 * @param truststoreLocation Vollqualifizierter Pfad zum Truststore oder
			leer lassen für Java-Default-Truststore
 * @param truststorePass Passwort für den Truststore. Wenn leer wird
			"changeit" ala Passwort benutzt.
 */
public ServerSSLAuthSocketFactory(String truststoreLocation,
								  String truststorePass){
	super();

	//Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
	if(!truststoreLocation.isEmpty()){
		caFile = new File(truststoreLocation);
	}else{
		//Pfad zum default Java TrustStore finden
		caFile = new File("cacerts");
		if (!caFile.exists() || !caFile.isFile()) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP
					+ "lib" + SEP + "security");
			caFile = new File(dir, "cacerts");
			if (!caFile.exists() || !caFile.isFile()) {
				caFile = new File(dir, "cacerts");
			}
		}
	}

	//Prüfen, ob ein TrustStore Password angegeben wurde
	String truststorePassword = truststorePass.isEmpty() ? "changeit" : truststorePass;

	//TrustStore öffnen
	try {
		InputStream in = new FileInputStream(caFile);
		ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, truststorePassword.toCharArray());
		in.close();
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (NoSuchAlgorithmException ex){
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (CertificateException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (FileNotFoundException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}catch (IOException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

	try {
		context = SSLContext.getInstance("TLS");
		tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
		context.init(null, new TrustManager[] {defTrustManager}, null);
		factory = context.getSocketFactory();
	} catch (NoSuchAlgorithmException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyManagementException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

}

Das sieht auf den ersten Blick recht komplex aus, ist aber im Grunde recht simpel.

  • In den Zeilen 23 - 28 prüfen wir ob der Konstruktor einen Pfad zu einem Truststore übergeben bekommen hat, und wenn dies nicht der Fall ist, wird der Pfad zu dem Standard-Java-Truststore in eurem JRE-Verzeichnis ermittelt.
  • Direkt danach in Zeile 41 prüfen wir ob ein Passwort übergeben wurde. Wenn keins übergeben wurde wird "changeit" genutzt. "changeit" ist das Defaultpasswort für den Standard-Java-Truststore.
  • In Zeile 45 - 48 öffnen wir den Keystore und laden dessen Inhalt in unsere Kokale KeyStore Variable.
  • Das Wichtigste geschieht nun in Zeile 67 - 72 hier holen wir uns einen Verweis auf den SSLContext (67) und erstellen uns mit Hilfe unseres KeyStores einene TrustManagerFactory(68-69). Anschließend lassen wir die TrustManagerFactory für uns einen neuen X509TrustManager "produzieren" der dann den Verweis auf unseren eigenen Keystore trägt. (70)
  • Nun sagen wir dem SSLContext, dass es unseren Trustmanager nutzen soll (71) für die darüber laufende Verbindungen und erstellen uns eine SSLSocketFactory (72)

Nun haben wir intern eine SocketFactory, die unseren eigenen (über den Konstruktor angegebenen) Keystore nutzt. Der Rest der Klasse die wir erstellt haben dient hierbei eigentlich nur als Wrapper und Durchreiche, der Wichtigste Teil war der Konstruktor.

Die restlichen Methoden können nun also alle Anfragen direkt an die produzierte SSLSocketFactory weiterleiten. So sieht dann die fertige Klasse aus:


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    /** Delegate {@link SSLSocketFactory}. */
    private transient SSLSocketFactory factory;

    private File caFile;
    private KeyStore ks;

    private SSLContext context;
    private TrustManagerFactory tmf;
    private X509TrustManager defTrustManager;
    private SavingTrustManager tm;

    /**
     * Neue Truststore-basierte SSL-SocketFactory
     * @param truststoreLocation Vollqualifizierter Pfad zum
	 *			Truststore oder leer lassen für Java-Default-Truststore
     * @param truststorePass Passwort für den Truststore.
	 *			Wenn leer wird "changeit" ala Passwort benutzt.
     */
    public ServerSSLAuthSocketFactory(String truststoreLocation,
				String truststorePass){
        super();

        //Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
        if(!truststoreLocation.isEmpty()){
            caFile = new File(truststoreLocation);
        }else{
            //Pfad zum default Java TrustStore finden
            caFile = new File("cacerts");
            if (!caFile.exists() || !caFile.isFile()) {
                char SEP = File.separatorChar;
                File dir = new File(System.getProperty("java.home") + SEP
                        + "lib" + SEP + "security");
                caFile = new File(dir, "cacerts");
                if (!caFile.exists() || !caFile.isFile()) {
                    caFile = new File(dir, "cacerts");
                }
            }
        }

        //Prüfen, ob ein TrustStore Password angegeben wurde
        String truststorePassword =
				truststorePass.isEmpty() ? "changeit" : truststorePass;

        //TrustStore öffnen
        try {
            InputStream in = new FileInputStream(caFile);
            ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(in, truststorePassword.toCharArray());
            in.close();
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex){
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }catch (IOException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

        try {
            context = SSLContext.getInstance("TLS");
            tmf = TrustManagerFactory.getInstance(TrustManagerFactory
													.getDefaultAlgorithm());
            tmf.init(ks);
            defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
            context.init(null, new TrustManager[] {defTrustManager}, null);
            factory = context.getSocketFactory();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyManagementException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String string,
					int i, boolean bln) throws IOException {
        return factory.createSocket(socket, string, i, bln);
    }

    @Override
    public Socket createSocket(String destinationAddress,
					int destinationPort) throws IOException, UnknownHostException {
        return (SSLSocket)factory.createSocket(destinationAddress, destinationPort);
    }

    @Override
    public Socket createSocket(String string, int i,
					InetAddress ia, int i1) throws IOException, UnknownHostException {
        return factory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return factory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
					InetAddress ia1, int i1) throws IOException {
        return factory.createSocket(ia, i, ia1, i1);
    }
}

Da wir nun eine Factory haben die uns Sockets liefert, die auf unseren Truststore zugreift, können wir diese nun nutzen um eine Verbindung zu SSL Servern aufzubauen. Das Schöne hierbei ist, dass Java an sehr vielen Stellen SocketFactories nutzt, so können wir fast in allen Szenarien unsere Klasse einsetzen.

So können wir unsere SSLFactory nun ganz simpel testen:


public static void main (String [] args) throws IOException{

    int port = 443; // default https port
    String host = "httpsurl";

    try {
      SSLSocketFactory factory
       = (SSLSocketFactory) new ServerSSLAuthSocketFactory("","");

      SSLSocket socket = (SSLSocket) factory.createSocket(host, port);

      // enable all the suites
      String[] supported = socket.getSupportedCipherSuites();
      socket.setEnabledCipherSuites(supported);

      Writer out = new OutputStreamWriter(socket.getOutputStream());
      // https requires the full URL in the GET line
      out.write("GET https://" + host + "/ HTTP/1.1\r\n");
      out.write("Host: " + host + "\r\n");
      out.write("\r\n");
      out.flush();

      // read response
      BufferedReader in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));

      // read the header
      String s;
      while (!(s = in.readLine()).equals("")) {
          System.out.println(s);
      }
      System.out.println();

      // read the length
      String contentLength = in.readLine();
      int length = Integer.MAX_VALUE;
      try {
        length = Integer.parseInt(contentLength.trim(), 16);
      }
      catch (NumberFormatException ex) {
        // This server doesn't send the content-length
        // in the first line of the response body
      }
      System.out.println(contentLength);

      int c;
      int i = 0;
      while ((c = in.read()) != -1 && i++ < length) {
        System.out.write(c);
      }

      System.out.println();
      out.close();
      in.close();
      socket.close();

    }
    catch (IOException ex) {
      System.err.println(ex);
    }
}

Tragt in Zeile 4 einfach den gewünschten SSL-Host ein wie z.B. Google oder Facebook und startet das kleine Programm. Falls ihr keine Parameter beim Erzeugen der Factory (Zeile 7 und 8) angegeben habt solltet ihr auch keine Probleme beim Verbinden haben und wenn das Programm durchgelaufen ist seht ihr die Antwort des Servers im HTML Format.

Falls ihr doch einen eigenen Truststore eingetragen habt müsst ihr sicherstellen, dass dieser den Public Key des Servers zu dem ihr euch verbinden wollt beinhaltet, sonst wird die Verbindung nicht erfolgen.

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

7Dez/110

SSL Teil 3: Der SSL Handshake

SSL Ist aktiv

SSL (Secure Socket Layer) sollte wohl den meisten ein Begriff sein, das ist dieses lustige kleine "s" was manchmal hinter dem "http" in der Adresszeile des Browsers auftaucht (siehe Bild). In dieser Serie möchte ich euch erklären wie SSL in der Theorie funktioniert und wie ihr es in euren Applikationen nutzen könnt.

In diesem Teil möchte ich etwas tiefer graben und mit euch zusammen den SSL Handshake durchgehen. Der gesamte Prozess, den der Client und der Server beim Verbindungsaufbau durchläuft nennt sich Handshake. Die Parteien tauschen also alle nötigen Informationen aus um eine Verbindung aufbauen zu können.

Ist der Handshake durchlaufen, so ist die Verbindung erfolgreich hergestellt und die verschlüsselte Kommunikation kann beginnen. Aber mal von vorn.

Eine verschlüsselte Verbindung, wie der Name schon sagt ist verschlüsselt, aber es gibt verschiedene Verschlüsselungsverfahren. So werden, wenn man von SSL spricht, eigentlich zwei Verfahren eingesetzt, nämlich zunächst die asymmetrische Verschlüsselung um den geheimen Schlüssel auszutauschen und dann die symmetrische Verschlüsselung (mit Hilfe des geheimen Schlüssels) um die eigentlich Kommunikation zu verschlüsseln.

Den gesamten Ablauf des Handshakes kann man ganz gut der Wikipedia Grafik entnehmen. Keine Angst, diese ist nicht so kompliziert wie sie aussieht.

Im ersten Schritt werden Zufallszahlen generiert und ausgetauscht. Anschließend schickt der Server seinen Public Key zum Client zur Prüfung und verlangt gleich auch ein Clientzertifikat (nur bei einer beidseitigen Authentifizierung).

Wenn der Client das Serverzertifikat akzeptiert schickt er seinen Public Key zum Server (bei einer beidseitigen Authentifizierung) welchen der Server nun prüfen (und akzeptieren) muss. Anschließend verschlüsselt der Client seine bisherigen Nachrichten mit seinem privaten Schlüssel und schickt diese an den Server, damit er prüfen kann, ob die bisher empfangenen Nachrichten auch wirklich von dem Client kamen und man nicht einer Man-In-The-Middle Attake o.ä. zum Opfer gefallen ist.

Nun erzeugt der Client mit Hilfe des Öffentlichen Schlüssels vom Server das so genannte Premaster Secret und schickt diesen an den Server. An dieser Stelle haben der Client und der Server alle wichtigen Informationen ausgetauscht und können nun beide das so genannte Mastersecret aus den am Anfang genereirten Zufallszahlen und dem Premaster Secret erzeugen.

Dieses Mastersecret wird nun dazu genutzt um die weitere Kommunikation symmetrisch zu verschlüsseln. Somit ist nun der SSL Handshake abgeschlossen und die Verbindung erfolgreich aufgebaut.

Im Grunde ist das alles auch ziemlich simpel, hat man den Ablauf ein paar mal im Kopf durchgespielt wird der recht logische Ablauf auch gut verständlich!

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

veröffentlicht unter: Allgemein keine Kommentare
5Dez/110

SSL Teil 2: Beidseitige Authentifizierung

SSL Ist aktiv

SSL Ist aktiv

SSL (Secure Socket Layer) sollte wohl den meisten ein Begriff sein, das ist dieses lustige kleine "s" was manchmal hinter dem "http" in der Adresszeile des Browsers auftaucht (siehe Bild). In dieser Serie möchte ich euch erklären wie SSL in der Theorie funktioniert und wie ihr es in euren Applikationen nutzen könnt.

Im letzten Teil haben wir die Serverseitige Authentifizierung besprochen. Heute wollen wir die Beidseitige Authentifizierung durchsprechen, welche auch Client Authentifizierung genannt wird.

Wenn man mit dem Browser im Internet unterwegs ist wird man eigentlich nie auf eine Beidseitig Authentifizierte SSL Verbindung treffen. Erst wenn man auf den Server und seine Architektur im Hintergrund schaut wird man diese Art von Sicherheit treffen.

Dabei ist das System hinter der Beidseitigen Authentifizierung ähnlich simpel wie bei der Serverseitigen. Wie der Name schon sagt, müssen sich diesmal beide Parteien gegenseitig authentifizieren. Wenn wir das nun auf unser Beispiel mit der Post aus dem ersten Teil anwenden siehts wie folgt aus: (Post ist der Server wir sind der Client)

Ihr kommt zur Post und wollt euer Paket abholen, nun ist erstmal die Post (als Server) gefragt und muss uns beweisen, dass es wirklich die Post ist und sendet uns ihr Zertifikat. Wir prüfen das Zertifikat und entscheiden, dass wir dem Zertifikat vertrauen.

Nun sagt die Post aber, dass sie auch einen Beweis dafür haben wollen, dass wir auch der sind, für den wir uns ausgeben und verlangt von uns ebenfalls ein Zertifikat. Wir senden nun also unser Zertifikat zur Post und warten auf die Bestätigung oder die Absage vom Server.

Erst wenn beide Seiten die gegenseitigen Zertifikate akzeptiert haben ist die verschlüsselte Verbindung aufgebaut und eine Kommunikation kann stattfinden. Sollte eine der Parteien unzufrieden mit dem Zertifikat sein wird der Vorgang abgebrochen und muss von vorn begonnen werden.

Wie ihr euch denken könnt ist diese Art von Authentifizierung viel sicherer als die Serverseitige, bei der sich nur eine Seite ausweisen muss, ist aber natürlich auch mit einem höheren Aufwand in der Administration verbunden. Das ist auch der Grund warum man beim Browsen nur auf Serverseitige Authentifizierung stößt.

Denn im Internet ist es eigentlich für uns uns Nutzer wichtig, dass wir mit Sicherheit wissen an wen wir unsere Daten senden. Der Server stellt unsere Identität normalerweise durch einen Login oder eine Anmeldung fest. Würde man eine Beidseitige Authentifizierung für das Internet wollen, müsste jeder Server da draussen unser Zertifikat kennen, und das wäre eine unlößbare Aufgabe ;)

Bei Internen Systemen ist die Sache da deutlich einfacher, da weiß man für gewöhnlich schon vorher, welche Systeme mit welchen kommunizieren müssen, und kann so die nötigen Zertifikate in die Systeme Pflegen. So geht man dann sicher dass nur diese Systeme miteinander Sprechen und sich nicht jemand ins System schleichen kann.

Typischer Aufbau

Typischer Aufbau

So baut man Server so auf, dass Sie auf der Access Seite (Internetanbindung) sich gegenüber dem Client authentifizieren, aber auf der internen Seite nur über beidseitige Authentifizierung an ihre Daten kommen.

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

veröffentlicht unter: Allgemein keine Kommentare
1Dez/110

SSL Teil 1: Serverseitige Authentifizierung und Zertifikate

SSL ist aktiv

SSL (Secure Socket Layer) sollte wohl den meisten ein Begriff sein, das ist dieses lustige kleine "s" was manchmal hinter dem "http" in der Adresszeile des Browsers auftaucht (siehe Bild). In dieser Serie möchte ich euch erklären wie SSL in der Theorie funktioniert und wie ihr es in euren Applikationen nutzen könnt.

Viele Bekannte Webportale wie Facebook und Google bieten alle ihre Dienste inzwischen komplett über SSL an. Wenn ihr diese Technologie nutzen wollt, müsst ihr verstehen wie diese Funktioniert und was da eigentlich im Hintergrund abläuft.

Dieses kleine "s" hat eine große Wirkung und sorgt dafür, dass die gesamte Kommunikation mit dem Server in verschlüsselter Form abläuft und niemand sehen kann was ihr da so treibt.

In 99% der Fälle sprechen wir hier über SSL über Serverseitige Authentifizierung, was soviel heißt wie der Server muss uns beweisen, dass er der ist für den er sich ausgibt. So wie wir unseren Ausweis vorzeigen müssen wenn wir ein Paket bei der Post abholen müssen weil der Postbote wiedermal zu faul war zu klingeln... Wir müssen der Post beweisen, dass wir der sind für den wir uns ausgeben.

Wie weist sich der Server denn nun aus? Einen Ausweis hat er nicht, aber er hat etwas sehr ähnliches, nämlich ein Zertifikat, das von einer dritten Stelle, nämlich einer CA (Certificate Authority - auf deutsch Zertifizierungsstelle) signiert wurde.

Wenn wir das auf unser Ausweisbeispiel anwenden sieht es ca so aus: Jeder kann sich ein Blatt Papier mit einem Foto drucken wo sein Name draufsteht, aber euer Paket bei der Post werdet ihr damit nicht bekommen, da die Post der Herkunft des "Ausweises" nicht traut, es muss also von anderen Person (der die Post vertraut), die nicht ihr selbst seid, bestätigt werden, dass das wirklich ein echter Ausweis ist und dass das wirklich ihr seid. Im Falle eines Ausweises würde das wohl das Einwohnermeldeamt machen o.ä. diese würden euch einen Ausweis austellen dem andere vertrauen, da sie wissen, dass es aus einer vertrauenswürdigen Quelle kommt.

Wenn ihr euch also zu einem Server verbindet, der SSL nutzen will wird dieser euch also zunächst sein Zertifikat senden (seinen Ausweis) und nun seid ihr in der Pflicht zu entscheiden, ob ihr diesem Zertifikat (und somit dem Server) vertraut oder nicht.

Die (Vertrauens)-Prüfung übernimmt für euch meistens der Browser selbst. Jeder Browser kommt von Haus aus mit einer Liste von CAs  denen er blind vertraut. Diese Liste könnt ihr auch einsehen, sie ist häufig irgendwo in den Einstellungen des Browsers vergraben. Im Firefox findet ihr diese unter Einstellungen -> Erweitert -> Verschlüsselung ->Zertifikate anzeigen -> Zertifizierungstellen. Das sieht dann ca so aus:

Vertrauenswürdige CAs

Das bedeutet, wenn ein Server dem Browser ein Zertifikat schickt, dass von einer dieser CAs signiert wurde, vertraut der Browser dem Server und baut die Verbindung zu ihm auf.

Was passiert denn nun wenn der Server uns ein unsigniertes Zertifikat schickt, oder ein Zertifikat dass von einer CA signiert wurde die dem Browser unbekannt ist?

Dann wird uns der Browser fragen, ob wir uns wirklich sicher sind, dass wir uns sicher sind, dass wir diese Seite wirklich aufrufen wollen. Dieses Fenster habt ihr (im FireFox) vielleicht auch schon gesehen:

Firefox ist misstrauisch

Nun müsst ihr also selbst entscheiden, ob ihr diesem Zertifikat vertraut. Hier sollte man aufpassen, denn wenn es um wichtige Sachen geht wie Online Banking sollte man von dieser Seite die Finger lasse, da es sich eventuell um ein Phishing versuch handeln könnte - diese Meldung ist niemals ein gutes Zeichen.

Wenn ihr hier das Zertifikat also akzeptiert, merkt der FireFox sich das und fragt bei der nächsten Verbindung nicht mehr nach da es für ihn ab nun ein vertrauenswürdiges Zertifikat ist.

Aber nicht nur die Browser haben einen solchen Zertifikate-Speicher, das Betriebssystem selbst hat ebenfalls eine Liste mit CAs denen es Vertrauen schänkt. Diese Liste kann in den Systemeinstellungen gefunden werden und wird sich häufig mit der des Browsers großenteils überschneiden. Unter Windows findest ihr diese unter Systemsteuerung -> Netzwerk und Internet ->Internetoptionen -> Inhalte -> Zertifikate -> Vertrauenswürdige Stammzertifizierungsstellen

Windows Vertrauens CAs

Was steht eigentlich in so einem Zertifikat? Gucken wir doch mal rein:

Was steht in einem Zertifikat?

Jedes Zertifikat enthält diese Informationen:

  • Anwendung (Ist es ein Server oder ein Clientzertifikat?)
  • Wer hat das Zertifikat ausgestellt?
  • Für wen wurde dieses Zertifikat ausgestellt
  • Wielang ist das Zertifikat gültig?
  • Die Signierungskette aller CAs die dieses Zertifikat signiert haben

An Hand dieser Informationen wird entschieden, ob das Zertifikat akzeptiert wird oder nicht.

Zertifikate sind hierbei nichts besonderes, da jeder sich selbst Zertifikate ausstellen kann, es gibts dutzende Tools im Netz die euch Zertifikate generieren können, wie z.B. das kleine Javaprogramm "keytool" dass automatisch mit einem Java SDK mitinstalliert wird. Das Wichtige an einem Zertifikat ist immer seine Signatur, denn erst diese macht ein Zertifikat wertvoll und für andere Vertrauenswürdig.

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

1Dez/111

iPad – braucht man das? Fazit nach drei Wochen

Vor ca. drei Wochen wurde ich mehr oder weniger überraschend Besitzer eines iPad2. Und wie wir Geeks technikaffine Menschen so sind, habe ich mich natürlich schon sehr darauf gefreut.

Als ich es dann endlich bekam, ausgepackt hatte, upgedatet hatte und es da so vor mir lag, habe ich angefangen die vorinstallierten Apps und Spielereien zu testen. So fiel natürlich sofort auf, dass das Userinterface viel angenehmen zu bedienen ist, das war bedingt durch das größere Display und die an die höhere Auflösung angepasste UI, die je nachdem wie man das Gerät hielt mehr oder weniger Optionen anbot.

Auch das Browsen im Netz bereitet einem deutlich mehr Vergnügen als auf einem kleinem Handy. Die Tabs im Safari sind deutlich einfacher zu bedienen und alles sitzt und macht einen Soliden Eindruck. Auch die Performance ist top.

Spiele und das System selbst laufen sehr flüssig, zum Testen habe ich mir Infinity Blade und Real Racing 2 zugelegt und man muss zugestehen, dass es schon Spaß macht auf einem größeren Display.

Was ebenfalls viel Spaß macht, ist es das iPad mit den Gesten zu bedienen. So kann man eine Applikation schließen indem man die Hand aufs Display legt und die Finger zusammenzieht, oder zwischen den Applikationen wechselt mit vier Fingern, das macht wirklich Spaß und sieht top aus! (Dieses Video zeigt es)

Des Weiteren hält der Akku ziemlich lange, ich habe das Gerät in diesen drei Wochen nur 2x geladen glaube ich.

Aber nachdem man etwas herumgespielt hat kommen aber auch schon die ersten Negativen Aspekte.

Was einem sehr schnell auffällt ist, dass die Apps die nicht an das iPad angepasst wurden in einer Art Emulator laufen den man auf die doppelte Auflösung stellen kann, was dann aber zur Folge hat, dass alles total verpixelt ist :(

Viele Apps sind inzwischen aber auch an das iPad und seine höhere Auflösung angepasst, das blöde daran ist dass die Entwickler sich das aber auch sehr gut bezahlen lassen, so kosten die an das iPad angepassten Apps meist das 3 bis 5-Fache der iPhone App, so ist das ein enormer Kostenaufwand wenn man seine Lieblingsapps auch auf dem iPad verwenden möchte. Dafür muss man aber auch sagen, dass die angepassten Apps dann aber auch (meistens) ihr Geld wert sind und die Bedienung deutlich vereinfachen und viel eyecandy bieten :)

Was mir aber (als Besitzer eines iPhone 4S) sofort ins Auge sprang war auch die sehr geringe Auflösung des Geräts, so kann man sehr leicht (wie auch beim iPone 3GS) jeden Pixel auf dem Display entdecken, was in einem wirklich starkem Kontrast zu dem schicken Retina-Display auf dem iPhone 4(S) steht.

Weiter gehts mit der Tastatur, das ist natürlich Geschmackssache, aber ich kann darauf nur schlecht Tippen. Auch wenn die Tasten fast so groß sind wie auf einer "richtigen" Tastatur, so fehlt einem doch das Gefühl eine Taste zu drücken. Dokumente damit zu erstellen oder zu bearbeiten ist in meinen Augen nicht drin. Das iPad ist ein reines Konsum-Gerät IMHO.

Was außerdem schade ist, ist dass wenn man bereits ein iOS Gerät sein Eigen nennt, man wirklich kaum was neues mit dem iPad geboten bekommt, da es im Grunde ein großes iPhone ist. So benutze ich das iPad inzwischen nurnoch gelegentlich wenn ich auf der Couch sitze und mich nicht zum Laptop bewegen will um etwas nachzugucken.

Unter der Woche fasse ich es kaum an, da ich einfach nicht weiß was ich damit machen soll. Darauf Bücher lesen? Nein danke, nicht mit dem Display.

So verbringt das iPad die meiste Zeit auf meinem Sreibtisch in der Ecke liegend und blinkt nur zwischendurch auf, wenn ich eine E-mail bekomme oder irgendwelche Erinnerungen aufpoppen, die das ipad sich aus der iCloud gezogen hat (was übrigens super funktioniert).

TL;DR

Um meine Frage in der Überschrift also zu beantworten, nein braucht man nicht, zumindest ich nicht, weswegen ich auch schon mit dem Gedanken spiele es zu verkaufen.

17Sep/110

Nice2Know: Attribut-basierende Routen in ASP.NET MVC

Für Routen ist in ASP.NET MVC für gewöhnlich die Global.asax zuständig, so werden hier alle Routen definiert und gemappt. Doch wenn man viele verschiedene Routen nutzen will wird es schnell voll in der eigentlich sehr übersichtlichen Klasse.

Da wäre es doch eigentlich cool wenn wir die Routen direct in unseren Controllern definieren könnten. Genau das können wir mit der Erweiterung AttributeRouting machen.

Installation

Installation über NuGet

Dank NuGet ist die Installation in wenigen Sekunden abgeschlossen und wir können unsere Actions mit Routing-Attributen dekorieren.

Die Attribute sind hierbei sehr einfach gestaltet und bieten uns alles was wir benötigen.


    public class DemoController : Controller
    {
        [GET("Ist/Das/Cool/Oder/Was")]
        public ActionResult Start()
        {
            return View();
        }
    }

Nun muss man nur noch die Attribut-basierten Routen registrieren:


    public static void RegisterRoutes(RouteCollection routes) {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapAttributeRoutes(); //--- das wars!

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Start",
                id = UrlParameter.Optional } // Parameter defaults
        );
    }

Und fertig! Nun können wir die Seite starten und unsere Route testen:

Das Ergebnis

Das Ergebnis

Die Routen die wir definieren können, beschränken sich nicht darauf, so können diese auch problemlos parametrisiert werden, inklusive Defaultparameter:


    public class DemoController : Controller
    {
        [GET("Artikel/{artikelid}/{version?}")]
        [RouteDefault("artikelid", "120")]
        public ActionResult Start(int artikelid, string version)
        {
            ViewBag.ArtikelId = artikelid;
            ViewBag.Version = version;

            return View();
        }
    }

Hier wird der Parameter artikelid mit einem Defaultwert ausgestattet und der zweite optional gemacht. Das Ergebnis kann sich sehen lasen:

Läuft!

Läuft!

Hier hört das Framework nicht auf, so könnt ihr auch Regular Expressions in die Routen integrieren und auch mehrere Routen an eine Action heften. Die Möglichkeiten sind grenzenlos ;)

Für weitere Infos schaut einfach auf der Projekthomepage vorbei, es lohnt sich!

11Sep/114

Lokale Wetterdaten anzeigen mit der HTML5 Geolocation API und ASP.NET MVC

Wenn man heutzutage hipp sein will mit seiner Webseite sollte man irgendwelche coolen Features von HTML5 nutzen. Deswegen möchte ich euch in diesem kleinem Tutorial zeigen, wie ihr auf eurer Seite dem aktuellen Besucher sein lokales Wetter anzeigen könnt.

Um dieses Ziel zu verwirklichen benötigen wir folgende Daten bzw. Dienste:

  1. Die physische Position des Besuchers
  2. Einen Dienst der uns zu der Position des Besuchers die Wetterdaten liefert

Der erste Punkt ist schnell abgehackt, denn hier werden wir eins der tollen neuen HTML5 Features namens Geolocation API benutzen. Diese API liefert uns, vorausgesetzt der Browser unterstützt es, Koordinaten des aktuellen Besuchers der Webseite. Alle aktuellen Browser, auch der IE ab Version 9 unterstützt diese API.

Das Ganze läuft so ab, dass wir als Webseite den Browser nach den Koordinaten fragen, aber dieser uns diese nicht sofort herausrückt, sondern erst den Benutzer fragt ob er einverstanden ist, das sieht je nach Browser unterschiedlich aus, hier ein Beispiel aus dem FireFox 6:

Wenn der Benutzer hier auf die “Zugriff erlauben” Schaltfläche klickt bekommen wir ein Objekt mit einem Haufen an Informationen rund um die Position des Benutzers. Für uns ist an dieser Stelle aber nur die Adresse des Nutzers von Bedeutung.

Moment mal, woher weiß der Browser so ich bin?

Tja, das ist eine gute Frage, auf magische Weise weiß der Browser wo wir uns aufhalten, auch wenn wir grad mit einem Desktopcomputer auf der Seite sind, der kein GPS Chip verbaut hat (im Vergleich zum Handy – wo das ja klar sein sollte) und das auch noch _ziemlich_ genau!

Im ersten Augenblick könnte man denken der Browser macht es über die IP Adresse, aber nein die IP ist _Viel_ zu ungenau im Vergleich mit den echten Koordinaten.

So sendet der Browser (Firefox) nach eurer Erlaubnis folgende Daten an Google unter dieser Adresse: https://www.google.com/loc/json


"mac_address": "01-23-45-67-89-ab",
"signal_strength": 8,
"age": 0,
"SSID": "MyAccessPoint"

Weitere Informationen

Als Google mit ihren StreetView Autos durch die Gegend gefahren ist haben die nämlich auch die Namen und die genauen Positionen von den WLAN Netzwerken gespeichert, an denen sie vorbeigefahren sind. An Hand dieser Informationen versucht Google euch nun zu orten.

Wie komme ich nun an diese Informationen?

Glücklicherweise gestaltet sich dies für uns sehr einfach:


if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(success, error);
} else {
  alert("Not Supported!");
}

Wenn der Browser diese API kennt werden die statischen Klassen Navigator und geolocation aufgerufen und hier die Methode getCurrentPosition aufgerufen, diese bekommt als Parameter einen Verweis auf die Methode die ausgeführt werden soll. Wenn das Ganze fehlschlägt (Kein Browsersupport) soll die display_error-Methode aufgerufen werden.

Die Funktion success bekommt einen Parameter mit, der alle relevanten Informationen enthält:
<pre>


function success(position) {
alert(position.coords.latitude);
alert(position.coords.longitude);
alert(position.address.city);
}

Für uns ist für das weitere Vorgehen aber nur der Name der Stadt wichtig.

Auslesen der Wetterinformationen

Es gibt diverse offene APIs die uns die Wetterdaten zu einer angegebenen Stadt liefern können, ich habe mich an dieser Stelle für die von Google entschieden, da diese offen ist und keine Registrierung voraussetzt.

Die API ist unter folgender URL zu erreichen:

http://www.google.com/ig/api?weather=[LOCATION]&hl=[LANGUAGE]

Hier müsst ihr lediglich die gewünschte Stadt und die gewünschte Sprache einsetzen. Als Antwort bekommt ihr eine XML, die die Wetterdaten enthält, und das nicht nur für jetzt, sondern für die gesamte nächste Woche, inklusive ausgeschriebener Formulierungen wie “Teils sonnig” oder “Meistens bewölkt”.

Und wenn euch das noch nicht genug ist, bietet Google auch gleich das passende Icon zu dem Wetter, dass ebenfalls aus dem XML.Status ausgelesen werden kann. Insgesamt gibt es 22 verschiedene Wetterzustände zu denen es jeweils auch ein passendes Icon gibt.

Moment, wie komme ich nun an diese Daten?

Es besteht nun nur noch ein kleines Problem. Die ganze Abfrage erfolgt beim Client, deswegen kennt nur der Client die Position des Benutzers. Wir müssen nun aber (im Hintergrund => Ajax) einen Request an den Google Dienst absenden um an die Wetterdaten zu kommen.

Leider können wir das nicht direkt aus dem Client machen, da die Browser es verbieten, Requests an andere Domains abzusenden als an die von der das Script geladen wurde. Das ganze schimpft sich “Same Origin Policy” und soll den Benutzer unter Anderem vor Cross-Site-Scripting-Attaken schützen.

Das bedeutet, dass wir den Umweg über den Server gehen müssen und uns auf dem Server eine kleine API anlegen müssen die unserem Script auf Anfrage die passenden Daten liefert.

Dies kann man z.B so implementieren:


private const string GOOGLE_WEATHER_API_URL =
	"http://www.google.com/ig/api?weather={0}&amp;amp;amp;amp;amp;amp;amp;hl=de";
public WeatherResult GetWeahterData(string city) {

    string api_url = string.Format(GOOGLE_WEATHER_API_URL, city);

    try {
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(api_url);
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
        StreamReader reader = new StreamReader(res.GetResponseStream());

        string response = reader.ReadToEnd();

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(response);

        string temp = xmlDoc.SelectSingleNode("xml_api_reply/weather/current_conditions/temp_c").Attributes["data"].Value;
        string icon = xmlDoc.SelectSingleNode("xml_api_reply/weather/current_conditions/icon").Attributes["data"].Value;

        WeatherResult wRes = new WeatherResult(temp, city);
        wRes.Icon = icon;

        return wRes;
    } catch (Exception ex) {
        return null;
    }
}

Für den Response habe ich mir eine kleine Klasse gebastelt, die ich zu Json serialisiere und als Antwort zurückgebe:


    public class WeatherResult {
        public string Temperature { get; set; }
        public string City { get; set; }
        public string Icon { get; set; }

        public WeatherResult(string temp, string city) {
            Temperature = temp;
            City = city;
        }
    }

Das Ganze wird von dem ApiController aufgerufen:


    public class ApiController : Controller
    {
        //
        // GET: /Api/GetWeather

        public JsonResult GetWeather(string id)
        {

            WeatherResult res = new Tools.Weather.WeatherHelper().GetWeahterData(id);

            return Json(res, JsonRequestBehavior.AllowGet);
        }

    }

Was wir nun noch machen müssen ist unsere neu geschaffene API über einen Ajax Request aufzurufen. Hier habe ich jQuery benutzt um das Ganze zu vereinfachen:


function get_address(position) {
    var full_url = "/Api/GetWeather/" + position.address.city;

    $.ajax({
        url: full_url,
        success: get_weather,
        error: display_error
    });
}

Die get_weather-Funktion auf die ich hier im Erfolgsfall verweise ist nun dafür verantwortlich die vom Server erhaltenen Daten anzuzeigen, und dank dem zu Json serialisiertem WeatherResult-Objekt kann dies auch bequem erfolgen. Hier habe ich ebenfalls jQuery genutzt um mir die benötigten Referenzen auf die DOM-Objekte zu besorgen. Ein mögliches Ergebnis des Ganzen könnte ihr hier rechts sehen.


function get_weather(address) {

    $(".weather_info").html(address.Temperature == "" ? "N/A" : address.Temperature + "°C");
    $(".location_info").html(address.City);

}

Sonst noch was?

Im Grunde wars das auch schon. Aber eine Kleinigkeit ist mir noch aufgefallen, und zwar kann es vorkommen, dass ihr keine Adressdaten von der Geolocation API bekommt, sondern nur Koordinaten. (Falls Google diese Daten nicht gelistet hat oder ähnliches).

In diesem Fall müsst ihr ein Reverselookup machen und die Koordinaten in eine Adresse umwandeln, da die Google Wetter API nur Ortsnamen, aber keine Koordinaten annimmt.

Glücklicherweise stellt und Google auch dafür eine API zur Verfügung, diese könnt ihr unter folgender URL erreichen:

http://maps.google.com/maps/api/geocode/json?latlng=[KOORDINATEN]sensor=true 

Hier könnt ihr sogar als Parameter angeben, ob ihr XML oder Json als Antwort haben wollt. Diese könnt ihr z.B. so nutzen:


private const string GOOGLE_MAPS_REVERSE_LOOKUP_URL =
	"http://maps.google.com/maps/api/geocode/json?latlng={0}&amp;amp;amp;amp;amp;amp;amp;sensor=true";
public static string GetCityFromCoords(string coords) {
    string api_url = String.Format(GOOGLE_MAPS_REVERSE_LOOKUP_URL, coords);

    try {
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(api_url);
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
        StreamReader reader = new StreamReader(res.GetResponseStream());

        string response = reader.ReadToEnd();

        var serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new[] { new DynamicJsonConverter() });

        dynamic obj = serializer.Deserialize(response, typeof(object));

        string city = obj.results[0].address_components[3].long_name;

        return city;
    } catch (Exception ex) {
        return "";
    }
}

Hier habe ich mir ein dynamic-Object generieren lassen aus dem übergebenen Json. Dazu habe ich diese Klasse hier genutzt.

Falls dieser Fall eintreffen sollte, braucht ihr übrigens nicht mit genauen Koordinaten bzw. Adressdaten zu rechnen, aber für unseren Zweck reicht es allemal.

kick it on dotnet-kicks.de

Get Adobe Flash playerPlugin by wpburn.com wordpress themes