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 "Date":
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:
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:
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.
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:
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.
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.
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!
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
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.
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
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
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:
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:
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
Was steht eigentlich in so einem Zertifikat? Gucken wir doch mal rein:
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
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.
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:
- Die physische Position des Besuchers
- 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"
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;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;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.
Ausgeben einer Meldung nach dem Anmelden des Benutzers an einer ASP.NET Webseite
Neulich habe ich auf einer meiner Seiten neben dem normalen Zugang für Benutzer auch einen Gastzugang eingerichtet.
Meldet sich jemand als Gast an meinem System an, stehen im alle Seiten des Adminbereiches zur Ansicht zur Verfügung, ein Speichern der Änderungen wird jedoch nicht erlaubt.
Um den Gastnutzer meiner Seite auf diese Einschränkungen und andere Besonderheiten hinzuweisen - wollte ich eine Meldung ausgeben - sobald der Gast sich beim System angemeldet hat.
Ich verwende zur Anmeldung der Benutzer die vom Visual Studio 2010 automatisch generierte Seite LogIn.aspx.
Im Folgenden möchte ich zeigen wie man diese automatisch generierte Seite abändern muss um oben beschriebenes umzusetzen zu können.
Die Meldung möchte ich als ModalPopup ausgeben. Hierfür erweiterte ich die LogIn.aspx um den Code für das ModalPopup
...
<div style="display:none;">
<asp:Button ID="ButtonGastLogIn" runat="server" />
</div>
<asp:ToolkitScriptManager ID="ToolkitScriptManagerGastLogIn" runat="server"/>
<asp:ModalPopupExtender ID="ModalPopupExtenderGastLogInt" runat="server"
TargetControlID="ButtonGastLogIn"
PopupControlID="PopupGastLogIn"
cancelControlID="ButtonClose"
backgroundcssclass="ModalPopupBG">
</asp:ModalPopupExtender>
<div id="PopupGastLogIn" runat="server">
...
<asp:imageButton ID="ImageButtonOK" runat="server" ImageUrl="~/image.png" />
</div>
Zu beachten ist hier, dass der Button zum Anzeigen des Popup nicht sichtbar ausgeführt wird.
Immerhin soll das Popup, abhängig davon ob sich ein normaler Benutzer oder ein Gast anmeldet, angezeigt werden oder eben nicht.
Die Codebehind-Datei wird wie folgt erweitert.
Protected Sub LoginUser_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs) Handles LoginUser.Authenticate
If ClassLogIn.CheckUser(Me.LoginUser.UserName, Me.LoginUser.Password) String.Empty Then
'Anmeldung ok
FormsAuthentication.RedirectFromLoginPage(Me.LoginUser.UserName, True)
Else
'Anmeldung fehlgeschagen
e.Authenticated = False
End If
End Sub
Diese Sub wird mit dem Drücken des Anmelden Buttons ausgeführt.
ClassLogIn.CheckUser ist eine von mir geschriebene Funktion die die Benutzereingaben prüft.
Sie werden vermutlich eine ähnliche Vorgehensweise gewählt haben, entscheidend für die Authentifizierung des Benutzers ist FormsAuthentication.RedirectFromLoginPage(…).
Dadurch wird der Benutzer angemeldet (authentication) und an die Ursprungsseite weitergeleitet.
Dieses Vorgehen wollen wir, für alle normalen Benutzer unseres Systems, weiterhin nutzen.
Es gilt nun zwischen „normalen Nutzern“ und „Gästen“ zu unterscheiden.
Da FormsAuthentication.RedirectFromLoginPage(..) den Response an die Ursprungsseite weiterleitet eignet sich dieses Vorgehen, zum Anmelden, für das Anzeigen eines ModalPopup nicht.
FormsAuthentication.SetAuthCookie(..) erfüllt unsere Bedürfnisse nach einer Authentifizierung ohne Weiterleitung perfekt. Zu beachten ist hier aber das die Authentifizierung erst vollständig ausgeführt wurde wenn ein Response stattgefunden hat.
Wir teilen die notwendigen Schritte also in zwei Teile auf.
Im ersten Schritt ändern wir unsere Sub von oben ein wenig ab.
Protected Sub LoginUser_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs) Handles LoginUser.Authenticate
If ClassLogIn.CheckUser(Me.LoginUser.UserName, Me.LoginUser.Password) String.Empty Then
'Anmeldung ok
If ClassLogIn.IsGast(Me.LoginUser.UserName) = True Then
FormsAuthentication.SetAuthCookie(Me.LoginUser.UserName, True)
Me.ModalPopupExtenderGastLogInt.Show()
Else
FormsAuthentication.RedirectFromLoginPage(Me.LoginUser.UserName, True)
End If
Else
'Anmeldung fehlgeschagen
e.Authenticated = False
End If
End Sub
Eine weitere Funktion prüft ob sich ein Gast oder ein normaler User am System anmelden will.
Handelt es sich um einen Gast, führen wir den ersten Teil der Authentifizierung aus und zeigen das ModalPopup an.
Wird das ModalPopup geschlossen leiten wir den Response an eine andere Seite unseres Systems weiter.
Damit wird der Anmeldevorgang abgeschlossen.
Protected Sub ImageButtonOK_Click(sender As Object, e As System.Web.UI.ImageClickEventArgs) Handles ImageButtonOK.Click
Server.Transfer("~/Default.aspx")
End Sub
Einen Nachteil hat das Ganze, ein Gast wird – im Gegensatz zu einem normalen Benutzer - immer auf eine bestimmte Seite (hier Default.aspx) weitergeleitet.
Über den Autor

Michael Bernhard
Hobby-Programmierer von VB.NET, C# und ASP.NET Anwendungen. Hauptberuflich als Inbetriebnehmer bei der Böwe Systec GmbH beschäftigt. Vorzeigbare Projekte – TimeZone Notifier und TSV Neusäß Junioren.
Google Plus Ersteindruck und Einladungen
Diese Woche kommt man ja an keiner Newsseite mehr vorbei ohne von Google Plus zu lesen. Ich habe dort inzwischen auch einen Account und kann die allgemeine Begeisterung tatsächlich teilen.
Bisher sind alle Features gut geordnet und leicht verständlich. Vorallem die Circles mit denen man seine Bekannten in Grüppchen unterteilt und somit genau kontrollieren kann wer Zugriff auf die Daten und Inhalte hat die man veröffentlicht sind sehr gut gelößt - da kann sich Facebook eine Scheibe abschneiden.
Da ich eh kein Facebook Freund bin freut es mich natürlich umsomehr dass Google nun eine Eigeninitiative gestartet hat und im Vergleich zu Buzz oder Wave ist das Konzept diemal durchdacht und darauf angesetzt die Schwächen der anderen Dienste auszunutzen.
Braucht Ihr noch Einladungen?
Wenn ihr noch eine Einladung zu Google+ braucht schreibt einfach ein Kommentar und füllt das E-Mail Feld aus, im Text Selbst braucht ihr die Email nicht zu erwähnen. Die angegebene Adresse sollte hierbei von Google sein, da es sonst nicht klappt.
Was zu tun nach der Einladung?
Das Einladungssystem ist momentan ausgeschaltet, deswegen kann man nur über einen kleinen Umweg Luete "Einladen". Nachdem ich euch eingeladen habe, bekommt ihr entweder eine Mail mit einem Link durch den ihr zu einer Seite gelangt wo ihr einen "Mitmachen" Knopf klicken könnt, oder es passiert einfach nichts. Wenn ihr keine Mail bekommt schaut alle paar Minuten auf https://plus.google.com vorbei, es sollte dann automatisch ein Mitmach-Button auftauchen.
Wenn das auch nicht hilft heißt es nur warten
Update: Laut dem letzten Tweet von Google ist die Anmeldung zu Google Plus nun wieder offen, einfach auf plus.google.com gehen und registrieren :)
Update 2: Doch nicht
Entstehung einer ASP.NET Webanwendung anhand eines Beispiels
Mit diesem Artikel will ich skizzieren wie eine Webanwendung mittels ASP.NET entworfen und umgesetzt wird. Ich will einen Einblick in die Vorüberlegungen, sowie in die Umsetzung des Projektes geben. Nicht mehr, aber auch nicht weniger.
Vorüberlegungen
Wie fast alle Hobbyprogrammierer erstelle auch ich eine Unzahl unnötiger und zum Teil auch unsinniger Anwendungen. Dies ist der Luxus den man sich leisten kann, wenn man damit nicht sein Geld verdienen muss.
Da ich ab und zu auf Dienstreise gehe, ist die aktuelle Uhrzeit am gegenwärtigen Standort immer ein Thema. Oft kommt auch die Frage auf, wie spät es denn bei den Lieben zu Hause wohl sein mag.
Hierfür kommt natürlich nur, mein neuestes Steckenpferd, eine Webanwendung in Frage.
Die ersten Überlegungen:
- Welcher Aufbau einer Webanwendung ist der beste
- Wie ermittle ich meinen aktuellen Standort
- Welche zusätzlichen Informationen kann ich noch anzeigen
1. Welcher Aufbau einer Webanwendung ist der beste?
Ich hab mich hier, sehr schnell, für eine Webanwendung entschieden, die ihre Informationen aus einem RSS-Feed bezieht. Vorteil hierbei, es handelt sich um ein XML-Format, welches leicht zu lesen ist und auch anderweitig verwendet werden kann.
Eine Windows-, Linux-, MacOS- oder Android Anwendung könnte den RSS-Feed ebenfalls nutzen.
2. Wie ermittle ich meinen aktuellen Standort
Im Prinzip wäre GPS das ideale Werkzeug um den aktuellen Standort auf der Welt bestimmen zu können. Da ich jedoch nur ein Notebook auf meinen Dienstreisen zur Verfügung habe, fällt das mit dem GPS aus, denn die wenigsten Notebooks verfügen über GPS – meines schon gar nicht.
Eine weitere Möglichkeit meinen Standort zu bestimmen wäre über die IP Adresse, mit der sich mein Computer über den jeweiligen Provider mit dem Internet verbindet. Nicht sehr genau, aber wenn es um die Zeitzone geht – genau genug.
Eine andere Alternative, die mir erst vor kurzem bekannt wurde, ist die Ermittlung des Standortes via HTML 5 – Voraussetzung hierfür sind ein Browser der diese neue Technologie unterstützt und aktiviertes JavaScript.
Die Umsetzung via HTML 5 ist zudem genauer als der Weg über die IP-Adresse. Ideal wäre also die Verknüpfung beider Techniken.
Da meine Seite aktuell mit der IP-Adresse arbeitet, möchte ich das als Basis der folgenden Abhandlungen nehmen.
3. Welche zusätzlichen Informationen kann ich noch anzeigen
Weitere Informationen bietet ASP.NET von allein. IP-Adresse, Sprache, Browser, Auflösung usw.
Die Umsetzung:
Wie schon erwähnt ist ein RSS-Feed nichts anderes als eine XML Datei. Diese wird als Antwort (Response) auf eine Anfrage (Request) vom Webservice zurückgegeben.
Im Prinzip also recht einfach
- Daten ermitteln
- XML erstellen
- Daten zurückgeben
Überlegung:
Welche Daten benötigen wir und welche Daten soll der RSS-Feed zusätzlich enthalten?
Im Prinzip benötigen wir nur drei Werte um die aktuelle Uhrzeit und Zeitzone übermitteln zu können.
- Die Zeitzone als ID – damit kann dann ganz einfach die passende Zeitzone auf dem Windows-PC gefunden werden.
(Wie das auf einem MAC OS oder einem Linux Rechner umgesetzt werden kann wäre eine weitere Überlegung wert) - Die Zeitverschiebung in Stunden (Als Basis gilt hier immer die koordinierte Weltzeit)
- Die Zeitverschiebung in Minuten (Als Basis gilt hier immer die koordinierte Weltzeit)
Alle weiteren Daten sind Beiwerk und blähen unseren RSS-Feed nur auf. Aber wer kommt denn schon ohne Beiwerk aus?
Interessant sind natürlich auch
- Die momentane IP-Adresse
- Die eingestellte Sprache
- Der aktuelle Ort (Stadt) bzw. das Land
- Die Region (hier kommen wir später darauf zurück)
- Die Geokoordinaten (geographische Länge und Breite)
Umsetzung:
Die erste Schwierigkeit besteht darin aus der IP-Adresse den aktuellen Standort zu ermitteln.
Hierfür gibt es eine Menge Webservices, die aber alle nicht sehr aktuell sind und ihre Daten nicht so einfach zur Verfügung stellen wollen.
Als die beste aller Möglichkeiten hat sich die MaxMind GeoLight City Datenbank in Verbindung mit dem Google Maps UserControl von SubGurim.net gezeigt.
Nachteil hier – die Datenbank ist 30MB schwer und muss jeden Monat aktualisiert werden, da sich die IP-Adressen ständig ändern. Was soviel heißt wie, je länger der Monat dauert je ungenauer die Angaben.
Abhilfe würde hier die kostenpflichtige Datenbank schaffen – diese ist zwar noch Größer wird aber in einem kürzeren Zyklus aktualisiert. Da ich das Ganze aber nur als Hobby-Projekt umsetzen will, sind mir die Kosten dafür zu hoch.
Die kostenpflichtige Datenbank gibt es ebenfalls hier (GeoCity) und kann natürlich auch mit dem gleichen UserControl betrieben werden.
Ich hab mich für das Binary Format der Datenbank entschieden, da diese Version in Verbindung mit dem SubGurim UserControl sehr schnell ist. Die Zugriffszeiten auf meinen RSS-Feed liegen bei etwa 0.5 Sekunden.
Das SubGurim Control wurde für ASP.NET 2.0 entwickelt, arbeitet aber auch im Framework 4 ohne Probleme.
Bindet man nun alles ein – ein Lesen der Dokumentation hilft wie immer weiter - reduziert sich der Code zur Ermittlung der Geokoordinaten auf
Dim location As Location = GetLocationFromIP(userIP)
Im Prinzip schon fast alles. Nur war es nicht unser Ziel die Geokoordinaten zu ermitteln, wir wollten die Uhrzeit und Zeitzone am aktuellen Standort ermitteln.
Anmerkung:
Die Klasse Location ist hier ein Teil des SubGurim.Net UserControls und enthält neben den GeoKoordinaten auch die ermittelte Stadt, das Land (beides als Klartext) und die Region als Kürzel (z.B. 02 = Bayern, 05 = Hessen).
Da ich für die nächsten Schritte keine weiteren Controls, Dll’s oder anderes frei verfügbares Werkzeug gefunden habe, musste ich die Verknüpfung der Standortdaten zur Zeitzone und das Decodieren des Regionkürzels selbst erledigen.
Überlegung:
Ein jeder Punkt dieser Welt gehört einer bestimmten Zeitzone an. Hier gilt es 3 Fälle zu beachten.
- Länder die als Ganzes einer Zeitzone angehören (z.B. Deutschland, Österreich, Schweiz)
- Länder die mehreren Zeitzonen angehören (z.B. USA, Russland, Indonesien)
- Gebiete die nicht erfasst werden können (Oceane und Wüsten)
1. Länder die als Ganzes einer Zeitzone angehören
Die einfachste Form, die Inhalte der Klasse Location können direkt zur Ermittlung der Zeitzone herangezogen werden.
Vereinfacht: Land = Zeitzone = Zeitverschiebung
2. Länder die mehreren Zeitzonen angehören
Nicht mehr ganz so einfach. Die Zeitzone wird hier anhand des Landes und der Region bestimmt.
Vereinfacht: Land (z.B. USA) - Region (z.B. CA - Kalifornien) = Zeitzone = Zeitverschiebung
Sonderfall: Eine Region enthält mehrere Zeitzonen (z.B. USA/Indiana – Eastern- und Central Time)
Hierfür habe ich noch keine Lösung – mir fehlen die genauen Daten – aber vermutlich wird es dann eine weitere Unterteilung anhand der ermittelten Stadt geben.
Land (z.B. USA) – Region (z.B. IN - Indiana) – Stadt (?) = Zeitzone = Zeitverschiebung
3. Gebiete die nicht erfasst werden können
Da es auf dem Meer oder in der Wüste keinen Provider bzw. keine Empfang gibt, kann dieser Fall ausgeschlossen werden.
Lösung hierfür wäre eine Ermittlung des Standortes via Geokoordinaten (GPS), was im Moment noch nicht umgesetzt wurde.
Die beschriebene Funktionalität hab ich in eine DLL gepackt, das alles beschreiben zu wollen würde hier aber zu weit führen.
Die Punkte 1. und 2. werden komplett durch eine List(of Structure) abgebildet, die alle Länder dieser Welt, inkl. den unter 2. beschriebenen Fällen, enthält.
Public Structure myTimeZone
Public Country As String
Public Region As String
Public TimeZoneInfo As TimeZoneInfo
Public Sub New(ByVal c As String, ByVal r As String, ByVal tzi As TimeZoneInfo)
Country = c
Region = r
TimeZoneInfo = tzi
End Sub
End Structure
Private mytimezones As New List(Of myTimeZone)
Die Klasse „TimeZoneInfo“ ist schon Bestandteil des Frameworks und bildet die auf dem jeweiligen System installierten Zeitzonen ab.
Die durch das SubGurim.Net UserControl bereitgestellten Informationen (Klasse Location) dienen somit als Filter für die Suche innerhalb der Liste. Die dadurch ermittelten Daten können nun den RSS-Feed füllen.
Erstellen eines RSS-Feeds:
Wie schon erwähnt ist ein RSS-Feed nichts anderes als eine XML-Datei welche durch eine Webseite zurückgegeben wird.
Dies kann auf vielfältige Art und Weiße geschehen.
Anfangs erstellte ich meinen Feed innerhalb einer WebForm (*.aspx). Später jedoch bin ich auf eine Methode gestoßen, die mir eleganter erschien, nämlich das Erstellen eines RSS-Feeds mittels eines „Generic Handlers“ (*.ashx)
Im Prinzip wird nichts anderes gemacht, als den kompletten Feed mittels LINQ zusammenzubauen und am Schluss als Response zurückzugeben.
Dim doc As New XDocument()
Dim rss As New XElement("rss")
rss.Add(New XAttribute("version", "2.0"))
Dim channel As New XElement("channel")
rss.Add(channel)
channel.Add(New XElement("title", "The TimeZoneNotifier RSS-Feed"))
channel.Add(New XElement("link", "http://TimeZoneNotifier.free-file-download.de"))
channel.Add(New XElement("description", "This site detects the timezone you're currently staying in !"))
channel.Add(New XElement("ttl", "60"))
Dim item As New XElement("item")
Channel.Add(item)
item.Add(New XElement("userIP", userIP))
item.Add(New XElement("userBrowser", userBrowser))
item.Add(New XElement("userLanguage", userLanguage))
item.Add(New XElement("userCity", userCity))
item.Add(New XElement("userRegion", userRegion))
item.Add(New XElement("userLongitude", userLongitude))
item.Add(New XElement("userLatitude", userLatitude))
item.Add(New XElement("userTimeOffsetHours", userTimeOffsetHours))
item.Add(New XElement("userTimeOffsetMinutes", userTimeOffsetMinutes))
item.Add(New XElement("CurrentUTCTime", CurrentUTCTime.ToString))
item.Add(New XElement("userTimeZone", userTimeZone))
doc.Add(rss)
(Den RSS-Feed wie er dann ausgegeben wird, sehen Sie am Ende dieses Artikels)
Sind wir erst einmal hier angekommen, wurde die meiste Arbeit schon erledigt. Der Rest besteht nur noch im Anzeigen der ermittelten Daten.
Anzeigen der Daten mittels einer WebForm:
Hier zum Verständnis, noch der Code für die Anzeige der Daten.
Da wir den Feed mittels einer Webanwendung abfragen, sind manche Informationen für den RSS-Feed nicht sichtbar. Bei diesen Daten verwerfen wir die Informationen die der RSS-Feed enthält einfach und füllen die Ausgabe mit den Werten der WebForm.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
if Postback Then
'RSS-Feed holen
Dim retRss As New Xml.XmlDocument
retRss.Load("http://timezonenotifier.free-file-download.de/rss.ashx?ip=" &amp; _
Request.UserHostAddress)
'IP-Adresse das Feeds stimmt
Me.LabelIP.Text = retRss.SelectSingleNode("/rss/channel/item/userIP").InnerText
'Wurde eine nicht gültige IP-Adresse erkannt -> Anzeigen der Daten abbrechen
If Me.LabelIP.Text = "No valid IP-Adress !!" Then
Exit Sub
End If
'Kein Browser im RSS-Feed vorhanden, da vom Server aus angefragt wird
Me.LabelBrowser.Text = Request.Browser.Browser
'Keine Sprache im RSS-Feed vorhanden, da vom Server aus angefragt wird
Try
Dim ci As New CultureInfo(Request.UserLanguages(0))
Me.LabelLanguage.Text = ci.DisplayName
Catch ex As Exception
Me.LabelLanguage.Text = "Unknown"
End Try
'erkannte Stadt
Me.LabelCity.Text =
retRss.SelectSingleNode("/rss/channel/item/userCity").InnerText
'erkannte Region
Me.LabelRegion.Text =
retRss.SelectSingleNode("/rss/channel/item/userRegion").InnerText
'erkannte Geogr. Länge
Me.LabelLong.Text =
retRss.SelectSingleNode("/rss/channel/item/userLongitude").InnerText
'erkannte Geogr. Breite
Me.LabelLat.Text =
retRss.SelectSingleNode("/rss/channel/item/userLatitude").InnerText
'erkannte Zeitverschiebung
Dim _offsethours As Integer =
CInt(retRss.SelectSingleNode("/rss/channel/item/userTimeOffsetHours").InnerText)
Dim _offsetminutes As Integer =
CInt(retRss.SelectSingleNode("/rss/channel/item/userTimeOffsetMinutes").InnerText)
'aktuelle Zeit am Standort berechnen
Dim tempRSS As String =
retRss.SelectSingleNode("/rss/channel/item/CurrentUTCTime").InnerText
Me.LabelCurrentTime.Text =
CDate(tempRSS).AddHours(_offsethours).AddMinutes(_offsetminutes).ToLongDateString &amp; " - " &amp; CDate(tempRSS).AddHours(_offsethours).AddMinutes(_offsetminutes).ToLongTimeString
Me.LabelCurrentTimeDiff.Text = _offsethours.ToString &amp; _
" h " &amp; _offsetminutes.ToString &amp; " min"
'erkannte Zeitzone
Me.LabelCurrentTimezone.Text =
retRss.SelectSingleNode("/rss/channel/item/userTimeZone").InnerText
Else
'kein Postback
End If
End Sub
Die Ergebnisse meiner Arbeit:
Das oben beschriebene hab ich unter http://timezonenotifier.free-file-download.de als ASP.NET Webseite veröffentlicht.
Den RSS-Feed gibt es unter http://timezonenotifier.free-file-download.de/rss.ashx
RSS-Feed (mein Standort)
<pre>
<rss version="2.0">
<channel>
<title>The TimeZoneNotifier RSS-Feed</title>
<link>http://TimeZoneNotifier.free-file-download.de</link>
<description>
This site detects the timezone you're currently staying in !
</description>
<ttl>60</ttl>
<item>
<userIP>79.249.177.227</userIP>
<userBrowser>Chrome</userBrowser>
<userLanguage>Deutsch (Deutschland)</userLanguage>
<userCity>Mering / Germany</userCity>
<userRegion>Bayern</userRegion>
<userLongitude>10,9833</userLongitude>
<userLatitude>48,2667</userLatitude>
<userTimeOffsetHours>2</userTimeOffsetHours>
<userTimeOffsetMinutes>0</userTimeOffsetMinutes>
<CurrentUTCTime>07.06.2011 09:40:14</CurrentUTCTime>
<userTimeZone>
(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien
</userTimeZone>
</item>
</channel>
</rss>
Quellen:
http://www.maxmind.com/app/geolitecity
http://de.googlemaps.subgurim.net/
http://lukencode.com/2010/05/19/ip-to-geo-location-in-asp-net-mvc/
http://www.aspcode.net/Creating-an-RSS-feed-for-your-ASPNET-site.aspx
Copyright:
Die Rechte der verwendeten Datenbanken und des UserControls liegt bei den Autoren.
Der angebotene RSS-Feed darf frei verwendet werden – eine Nennung der Quelle des Feeds bzw. der Adresse dieses Artikels wird vorausgesetzt.
Ein Dank geht an Sebastian Gross der mir einige Anregungen gegeben hat.
Dieser Service bleibt kostenlos, die Bedingungen zur Nutzung des RSS-Feed können sich jedoch jederzeit ändern.
Über den Autor

Michael Bernhard
Hobby-Programmierer von VB.NET, C# und ASP.NET Anwendungen. Hauptberuflich als Inbetriebnehmer bei der Böwe Systec GmbH beschäftigt. Vorzeigbare Projekte – TimeZone Notifier und TSV Neusäß Junioren.
Benutzerregistrierung mit ASP.NET MVC und jQuery Teil 2
Im ersten Teil haben wir die Ajax-Funktionalität mit Hilfe von jQuery “zu Fuß” implementiert und haben nur die einfachsten Mittel verwendet. In diesem zweiten Teil wollen wir das nun mit Hilfe des ASP.NET MVC3 Frameworks implementieren, welches uns einiges an Arbeit abnimmt.
Das schöne an der dritten Version des MVC Frameworks ist, dass Microsoft hier schon einige helfende Elemente zum Validieren von Clientangaben eingebaut hat, die wir oft mit nur einer kleinen Zeile Code oder einem Attribut aktivieren können.
Fangen wir also mit unserem Model an und erweitern es um die neuen Attribute die uns das Framework zur Verfügung stellt:
public class RegisterModel2 {
[Required(AllowEmptyStrings = false,
ErrorMessage = "Bitte geben Sie einen Benutzernamen an!")]
[DisplayName("Benutzername")]
[Remote("ValidateUsername", "Validation")]
public String UserName { get; set; }
[Required(AllowEmptyStrings = false,
ErrorMessage = "Bitte geben Sie eine Emailadresse an!")]
[DisplayName("E-Mail Adresse")]
[Remote("ValidateEmail", "Validation")]
public String UserMail { get; set; }
[Required(AllowEmptyStrings = false,
ErrorMessage = "Bitte geben Sie ein Passwort an!")]
[DisplayName("Passwort")]
[DataType(DataType.EmailAddress)]
[Remote("ValidatePassword", "Validation")]
public String UserPass { get; set; }
[Required(AllowEmptyStrings = false,
ErrorMessage = "Bitte wiederholen Sie ihr Passwort!")]
[Compare("UserPass", ErrorMessage="Die Passwörter stimmen nicht überein!")]
[DisplayName("Passwort wiederholen")]
public String UserPass2 { get; set; }
}
Wie man sieht sind nun einige Attribute dazugekommen, die Eigenschaften der Klasse an sich haben sich aber nicht verändert! Aber mal langsam, was bedeuten diese Attribute eigentlich?
- Required
- Markiert die Eigenschaft als Pflichtfeld. Durch setzen dieses Attributs wird beim Absenden des Formulars automatisch geprüft ob diesem Feld ein Wert zugewiesen wurde, falls nicht wird eine Fehlermeldung generiert.
- Durch die Option AllowEmptyStrings = false sorgen wir dafür, dass auch Werte die nur als Leerzeichen bestehen ebenfalls Fehler auslösen.
- Mit dem Setzen der ErrorMessage Eigenschaft können wir eine Eigene Fehlermeldung definieren, die statt der Standardmeldung angezeigt wird. Lässt man diese Eigenschaft weg wird eine Meldung generiert die ca. so ausschaut: “Das Feld "FELDNAME" ist erforderlich.”
- DisplayName
- Dieses Attribut haben wir auch schon in dem Letzten Teil benutzt. Dieses sorgt dafür dass wir den Namen für das Eingabefeld, dass der Benutzer später im Browser zu sehen bekommt bereits im Model definieren können.
- Remote
- Durch dieses Attribut definieren wir welche Methode und welcher Controller für das Validieren der Eigenschaft verantwortlich ist. Erst wird die Action und als zweiter Parameter der Controller angegeben. Wenn der Benutzer später Eingaben in das Textfeld tätigt, wird diese Action aufgerufen und der eingegebene Wert als Parameter übergeben, sehr ähnlich wie wir es in Teil 1 gemacht haben, aber später mehr dazu.
- DataType
- Damit kann man den Inhalt bestimmen was einem beim Validieren helfen kann.
- Compare
- Mit diesem Attribut können wir angeben, dass diese Eigenschaft den gleich Wert haben muss wie eine andere. Dies wird dann automatisch von jQuery überprüft, somit müssen wir nicht mehr machen als dieses Attribut zu setzen.
- Als Parameter übergeben wir den Namen der Eigenschaft mit der der Wert verglichen werden soll und, als zweiten setzen wir eine eigene Fehlermeldung damit eine vernünftige Fehlermeldung angezeigt wird.
Damit haben wir unser Model fertiggestellt, nun schauen wir uns an was wir an unserer View verändern müssen.
Die gute Nachricht ist, dass wir den kompletten JavaScript/jQuery Teil streichen können, da das für uns alles automatisch geregelt wird, so sieht nun unsere View aus:
@using (Html.BeginForm("Submit", "Validation")) {
<table border="1">
<tr><td>@Html.LabelFor(model => model.UserName):</td>
<td>@Html.TextBoxFor(model => model.UserName)</td>
<td>@Html.ValidationMessageFor(model => model.UserName)</td>
</tr>
<tr><td>@Html.LabelFor(model => model.UserMail):</td>
<td>@Html.TextBoxFor(model => model.UserMail)</td>
<td>@Html.ValidationMessageFor(model => model.UserMail)</td>
</tr>
<tr><td>@Html.LabelFor(model => model.UserPass):</td>
<td>@Html.TextBoxFor(model => model.UserPass)</td>
<td>@Html.ValidationMessageFor(model => model.UserPass)</td>
</tr>
<tr><td>@Html.LabelFor(model => model.UserPass2):</td>
<td>@Html.TextBoxFor(model => model.UserPass2)</td>
<td>@Html.ValidationMessageFor(model => model.UserPass2)</td>
</tr>
<tr><td> </td>
<td><input id="submit_form_button" type="submit"
value="Registrierung abschicken" /></td>
<td> </td>
</tr>
</table>
}
Wenn ihr euch den Code anschaut, werdet ihr feststellen, dass sich eigentlich nicht getan hat. Das Einzige was sich verändert hat ist dass nun das Span in dem wir im letzten Teil die Fehler ausgegeben haben verschwunden ist und stattdessen
@Html.ValidationMessageFor(model => model.xxxxx)
aufgetaucht. Wie man sich auch schon von dem Namen ableiten kann wird hier eine Validierungsnachricht angezeigt, also mit anderen Worten die Fehlermeldung die bei der Validierung dieses Feldes entsteht. Diese Fehlermeldungen haben wir zum Teil schon über die Attribute im Model definiert.
Das wars auch schon mit Änderungen.
super oder? Wir haben nun einen Haufen Code rausgeworfen, ein paar Attribute eingefügt und nicht eine Zeile JavaScript geschrieben - perfekt ![]()
Aber noch sind wir nicht ganz fertig, denn wir haben nun gar nicht die Regeln für die Validierung des Benutzernamen, der Emailadresse und des Passworts implementiert.
Wenn ihr aufgepasst habt werdet ihr euch daran erinnern, dass wir das Attribut [Remote] verwendet haben und dort als Parameter den Namen einer Action angaben, an die die Informationen zur Validierung gesendet werden sollten.
Diese Actions müssen wir nun natürlich in unserem Validation Controller implementieren, zu unserem Glück unterscheiden sich diese Methoden nur minimal von denen die wir bereits im letzten Teil erstellt haben:
public ActionResult ValidateUsername(string UserName) {
//Wenn in unserer "Datenbank" der Name bei einem anderen Benutzer Auftaucht
if (new UserDatabase().Users.Where(u => u.Username.Equals(UserName)).ToList().Count > 0) {
//Eine Fehlermeldung zurückgeben
return Json("Dieser Benutzername ist schon vergeben", JsonRequestBehavior.AllowGet);
}
//Wenn der Benutzername kürzer als 5 Zeichen ist ebenfalls Fehler ausgeben
if (UserName == null || UserName.Length < 5) {
//return "Der Benutzername ist zu Kurz!";
return Json("Der Benutzername ist zu kurz!", JsonRequestBehavior.AllowGet);
}
//Falls es nichts zu meckern gibt geben wir true zurück
return Json(true, JsonRequestBehavior.AllowGet);
}
Was ist passiert? Die Logik der Funktion ist gleich geblieben, lediglich der Rückgabetüp und die Rückgabemethode hat sich geändert. Wir geben nun ein ActionResult statt eines einfachen Strings zurück. Dazu rufen wir die Methode Json auf der wir als Parameter die Daten übergeben die übertragen werden sollen und als zweites erlauben wir GET Aufrufe.
Die Antwort die dadurch erzeugt wird wird automatisch auch in ein Format umgewandelt die jQuery versteht. da wir in diesem Fall aber nur ein Boolean oder einen String übergeben ist dies nicht nötig.
Wenn wir einen String ausgeben wird das als Ausgabe für die Fehlermeldung genutzt. Alternativ können wir auch ein false ausgeben, dann wird die Standardfehlermeldung angezeigt, die nicht allzu schön ist. Durch die Angabe von true sagen teilen wir jQuery auf der Clientseite mit, dass kein Fehler aufgetreten ist und dass es die eventuell angezeigte Fehler ausblenden soll.
Die anderen zwei Validierungsmethoden müssen genauso geändert werden um problemlos mit dem [Remote] Attribut genutzt zu werden wodurch wir den Vorteil erhalten, dass wir kein JavaScript Code schreiben müssen, alles wird von den Frameworks automatisch geregelt.
An dieser Stelle lohnt es sich mal anzuschauen was da eigentlich generiert wird für uns. Starten wir also unsere MVC Applikation und gucken mal in den Quelltext:
<td><label for="UserName">Benutzername</label>:</td> <td><input data-val="true" data-val-remote="&amp;amp;#39;Benutzername&amp;amp;#39; is invalid." data-val-remote-additionalfields="*.UserName" data-val-remote-url="/Validation/ValidateUsername" data-val-required="Das Feld &amp;amp;quot;Benutzername&amp;amp;quot; ist erforderlich." id="UserName" name="UserName" type="text" value="" /> </td> <td><span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span> </td>
Oben ist der Ausschnitt zu sehen, der für das Benutzername-Eingabefeld verantwortlich ist inklusive dem dazugehörigen Label und dem Validierungstext.
Auffällig ist, dass kein JavaScript generiert wurde, sondern das Input-Element mit zusätzlichen Tags versehen wurde, die aber nur für jQuery von Bedeutung sind. Andere Applikationen oder Frameworks werden dadurch also nicht beeinflusst, diese ignorieren einfach diese für sie unbekannte Attribute.
Wenn man die Attribute etwas genauer betrachtet wird man einiges aus unserem Model wiederfinden, wie zum Beispiel unsere dort definierte Fehlermeldung.
Nun bleibt nur noch eine Kleinigkeit zu tun, nämlich das nötige CSS zu schreiben dass mit der jQuery Validierung zusammenspielt und die nötigen JavaScript Dateien zu importieren.
Fangen wir mit dem Importieren der JavaScript Bibliotheken an. Wenn ihr ein neues MVC3 Projekt anlegt habt ihr schon alle Dateien die nötig sind bereits in eurem Scriptordner. Öffnet die “_Layout.cshtml” und fügt (falls noch nicht vorhanden) folgende Imports hinzu:
<script src="@Url.Content("~/Scripts/jquery-1.4.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
Diese Imports werden nun durch diese Mastervorlage in jede View eingebunden. Natürlich könnt ihr diese Imports auch explizit nur in die View schreiben in der diese auch benötigt werden, das wäre sogar vorteilhafter denke ich.
Zu guter Letzt kümmern wir uns um das CSS. Auch hier generiert euch Visual Studio das nötige CSS wenn ihr ein neues MVC Projekt anlegt, auch wenn ihr als Vorlage ein leeres MVC Projekt wählt. In dem Ordner Content werdet ihr eine Datei namens Site.css finden in der die CSS Klassen für die Validierung stehen:
/* Styles for validation helpers
-----------------------------------------------------------*/
.field-validation-error
{
color: #ff0000;
}
.field-validation-valid
{
display: none;
}
.input-validation-error
{
border: 1px solid #ff0000;
background-color: #ffeeee;
}
.validation-summary-errors
{
font-weight: bold;
color: #ff0000;
}
.validation-summary-valid
{
display: none;
}
jQuery greift auf diese Klassen zurück um das Aussehen der Eingabefelder entsprechend dem Validierungsergebnis anzupassen. Hier könnt ihr nun eure Eigenen Änderungen vornehmen und die Visualisierung euren Wünschen anpassen.
Ich habe hier noch, um das Beispiel aus Teil 1 nicht aus den Augen zu verlieren die CSS Klasse etwas erweitert um das Aussehen anzugleichen:
.input-validation-error
{
border: 1px solid #ff0000;
background-color: #ffeeee;
background-image: url("../Grafik/cancel.png");
background-position: right;
background-repeat: no-repeat;
}
Wenn man die Applikation nun startet funktioniert das Beispiel dem aus Teil 1 sehr ähnlich:
Damit haben wir es eigentlich schon geschafft. Zum Schluss noch ein kleiner Hinweis. Natürlich ist dieser Weg um einiges einfacher, aber es gibt auch einen kleinen Haken, nämlich dass das Framework begrenzt ist. Das bedeutet, dass man nicht alles darüber schnell und ohne selber Code zu schreiben lösen kann, viele Anforderungen müssen immer noch von Hand oder mit Hilfe von anderen Frameworks implementiert werden.
Das erweiterte Demoprojekt aus Teil 1 gibts hier: Download [VS2010 c#]
Benutzerregistrierung mit ASP.NET MVC und jQuery Teil 1
Wer heute Webanwendungen erstellt kommt einfach nicht drumherum JavaScript zu verwenden um dem Benutzer zb. die Eingabe von Daten in ein Formular zu erleichtern und zB. Hinweise während der Eingabe anzuzeigen.
Leider ist JavaScript bei vielen nicht wirklich beliebt und die meisten versuchen wann immer es geht darauf zu verzichten, und das auch aus guten Gründen, denn ähnlich wie mit dem HTML Standard arbeiten die verschiedenen Browser in einigen Bereichen etwas eigenwillig, das kann einen schon mal zur Verzweifelung treiben.
Das haben sich auch viele andere gedacht und so gibt es inzwischen viele Frameworks die dem Entwickler viel Arbeit abnehmen und sich um diese fiesen Sachen wie die Kompabilität zu Browsern kümmern. In meinem Beispiel hier möchte ich euch das JavaScript Framework jQuery vorstellen.
Warum jQuery?
jQuery ist inzwischen in der Version 1.5.1 verfügbar und ist kompatibel zu allen Browsern, desweiteren ist die Community sehr groß und es existieren hunderte von Plugins die ich nutzen könnt. Aber die wirklich guten Gründe sind, dass es über eine sehr sehr gute Dokumentation verfügt, alle Browser inklusive des IE9 unterstützt und offiziell von Microsoft supportet wird. Wenn ihr also beispielsweise ein neues ASP.NET (MVC) Projekt erstellt werdet ihr feststellen, dass ihr im Scripts-Ordner bereits die jQuery Scripte hinterlegt habt.
Was macht dieses Framework eigentlich?
jQuery wie der Name es schon sagt zeigt seine ganze Stärke in der Auswahl bzw. der Selektion von Webseitenelementen im DOM, so kann man mit einfachen Einzeilern ganze Webseiten-Bereiche modifizieren. Außerdem bietet jQuery Funktionen die man häufig nutzt wie zB. Asynchrone Ajax aufrufe oder Animationen von Feldern. Aber gut kommen wir zu unserem Beispiel.
Unser Beispiel
Als Beispiel möchte ich hier ein einfaches Registrierungsformular erstellen, dass wir dann mit jQuery aufwerten wollen. Dabei möchte ich hier den manuellen Weg gehen und nicht die in ASP.NET MVC3 integrierten Methoden nutzen, eventuell können wir ja ein einem zweiten Teil dann den anderen Weg darstellen.
Beginnen wir erstmal mit dem Model. Da wir unsere Registrierung simpel halten wollen muss der Benutzer nur drei Angaben machen, nämlich seinen Namen, Email Adresse und ein Passwort. Normalerweise würden wir unser model hier ca so gestalten:
public class RegisterModel {
[Required]
[DisplayName("Benutzername")]
[Remote("CheckUserName", "Validation")]
public String UserName { get; set; }
[Required]
[DisplayName("E-Mail Adresse")]
[DataType(DataType.EmailAddress)]
[Remote("CheckUserEmail", "Validation")]
public String UserMail { get; set; }
[Required]
[DisplayName("Passwort")]
[Remote("CheckUserNPassword", "Validation")]
public String UserPass { get; set; }
[Required, Compare("UserPass")]
[DisplayName("Passwort wiederholen")]
public String UserPass2 { get; set; }
}
Da wir das aber selber machen wollen und teils clientseitig validieren wollen schwächen wir das mal etwas ab und nehmen alles raus bis auf das DisplayName Attribut:
public class RegisterModel {
[DisplayName("Benutzername")]
public String UserName { get; set; }
[DisplayName("E-Mail Adresse")]
public String UserMail { get; set; }
[DisplayName("Passwort")]
public String UserPass { get; set; }
[DisplayName("Passwort wiederholen")]
public String UserPass2 { get; set; }
}
So, nachdem wir das Model nun fertig haben, bauen wir das Projekt ein Mal und erstellen unsere View. Hier habe ich eine simple Tabelle angefertigt, die die nötigen Form-Elemente beinhaltet, die der Benutzer ausfüllen soll:
<div id="register_form"> <div id="headline">Registrierung</div> � <form id="registration_form" action="/Register" method="post"> <table border="1"> <tbody> <tr> <td>@Html.LabelFor(model => model.UserName):</td> <td>@Html.TextBoxFor(model => model.UserName)</td> <td><span id="UserName_error"> </span></td> </tr> <tr> <td>@Html.LabelFor(model => model.UserMail):</td> <td>@Html.TextBoxFor(model => model.UserMail)</td> <td><span id="UserMail_error"> </span></td> </tr> <tr> <td>@Html.LabelFor(model => model.UserPass):</td> <td>@Html.TextBoxFor(model => model.UserPass)</td> <td><span id="UserPass_error"> </span></td> </tr> <tr> <td>@Html.LabelFor(model => model.UserPass2):</td> <td>@Html.TextBoxFor(model => model.UserPass2)</td> <td><span id="UserPass2_error"> </span></td> </tr> <tr> <td></td> <td><input id="submit_form_button" type="submit" value="Registrierung abschicken" /></td> <td></td> </tr> </tbody> </table> � </form> � </div>
Hier benutze ich die neue Razor Syntax von ASP.NET MVC3 falls ihr damit noch nicht vertraut seid könnt ihrhier mal gucken.

- So sieht das Beispielformular aus
Die erste Spalte enthält hier jeweils die Beschreibung des Feldes, die zweite das Input-Element und die Dritte ist für die Anzeige von Fehlern zuständig, zu Beginn aber leer!
Der jQuery Part
So weit so gut, nun haben wir eigentlich schon alles was wir benötigen, und wenn wir nun nicht die Absicht hätten JavaScript zu benutzen wären wir an dieser Stelle schon fertig, müssten nur noch den Controller Part implementieren und könnten alles zu den Akten legen. Aber wir wollen dem Benutzer hier eine möglichsts angenehme Erfahrung bieten, denn vielleicht habt ihr es schon selbst erlebt und musstet euch auf einer Seite anmelden wo die Anmeldung ein Krampf war, das kann einem schon mal den einen oder anderen User kosten!
Was wollen wir hier nun also verbessern?
- Wenn der Benutzer einen Benutzernamen eingibt prüft das System im Hintergrund automatisch ob dieser Name schon vergeben ist und markiert das Eingabe Feld entsprechend.
- Nach der Eingabe der Email-Adresse wird diese automatisch geprüft nach Format und Verfügbarkeit und der Benutzer bekommt signalisiert ob damit alles in Ordnung ist, natürlich alles im Hintergrund
- Wenn der Benutzer sein Passwort wählt prüften wir ob dessen Stärke für uns ausreicht und ob beide Passwortfelder auch das gleiche Passwort beinhalten
- Der Absenden-Button wird erst anklickbar, nachdem alle Felder so ausgefüllt sind wie wir es uns vorstellen
Alles das können wir dank jQuery sehr einfach erreichen. Fangen wir mal vorne mit Punkt 1 an.
Erst ein mal zu dem allgemeinen Ablauf, wie soll das Ganze funktionieren?
- Also der Benutzer tippt seinen Wunschnamen ein,
- und wechselt aus dem Benutzername-Eingabefeld.
- Nun soll unsere Webseite den eingegebenen Wert nehmen, und diesen an den Server senden
- Der Server prüft den Namen auf Verfügbarkeit oder Form und Verbotene Zeichen etc
- Und sendet eine Information zurück. Diese Information ist entweder ein Fehler oder eine Erfolgsmeldung
- Je nachdem ob ein Erfolg oder ein Fehler vermeldet wird wird eine Fehlermeldung angezeigt und das Eingabefeld markiert
Klingt doch eigentlich ganz logisch! Fangen wir an. Erst einmal benötigen wir die jQuery Scriptdatei, diese bekommen wir direkt auf der Startseite von www.jquery.com Rechts oben auf der Startseite könnt ihr zwischen der Production und der Development Version wählen. Der unterschied besteht nur darin, dass die Production Version stark komprimiert ist und somit weniger Traffic verursacht, wenn ihr recht frisch seit was jQuery und javascript angeht solltet ihr euch die Development Version laden, da diese besser zu debuggen ist!
Nachdem ihr das Script geladen habt müssen wir das in unser Projekt einbinden. Zieht die js-Datei einfach aus dem Windows Explorer per Drag & Drop in euer Scripts-Ordner im Projekt Explorer. Nun fügen wir noch die Referenz auf die Datei hinzu, das machen wir am besten in der _Layout Datei die im Shared-Verzeichnis des View Verzeichnisses liegt. Hier ist wahrscheinlich schon ein Verweis auf ein jQuery Script vorhanden, ihr müsst nur noch den Pfad anpassen:
<script src="@Url.Content("~/Scripts/jquery.1.5.1.min.js")" type="text/javascript"></script>
Diese _Layout Datei wird automatisch in alle unsere Views eingebunden (zur Laufzeit), deswegen müssen wir den Verweis nur an dieser Stelle einfügen, praktisch oder?
Nachdem das geschafft ist können wir uns dem eigentlichen Teil widmen. Wechseln wir also wieder zurück zu unserem Login Formular und machen uns an unseren Punkt 1.
Bevor wir starten möchte ich hier nochmal kurz erwähnen, dass das ein Anfänger Tutorial ist und wir die Kommunikation mit dem Server sehr einfach gestalten werden, eventuell werde ich dann noch einen weiteren Teil machen in dem wir die Kommunikation mit JSON realisieren.
Fangen wir an
JQuery ist nichts anderes als Javascript, ihr habt sicherlich auch schon Javascript Dateien erstellt und diese dann in eure Webseiten eingebunden, hier machen wir nichts anderes, ihr könnt ja auch einfach in die jQuery Datei reinschauen, es ist "nur" javascript.
Wenn wir Funktionen von jQuery benutzen wollen können wir das tun in dem wir das Dollarzeichen benutzen "$" oder jQuery ausschreiben, so funktioniert beides:
$.ajax(...);
jQuery.ajax(...);
Ich nutze nur die $-Schreibweise, einfach nur weil es kürzer ist, es ist jedoch euch überlassen was ihr lieber mögt.
Mit jQuery ist sehr einfach Seitenelemente im DOM auszuwählen, so genügt eine einfache Angabe der Klasse, ID oder des Elementen-Typs um diese auszuwählen. Hier ein paar Beispiele:
<script type="text/javascript">
//Wählt das Element mit der ID username aus und setzt die Hintergrundfarbe auf rot
$('#username').css('background-color', 'red');
//Wählt ALLE Elemente mit der Klasse link aus und setzt die Hintergrundfarbe auf rot
$('.link').css('background-color', 'red');
//Wählt ALLE Input-Elemente aus und setzt deren Hintergrundfarbe auf rot
$('input').css('background-color', 'red');
</script>
Das obere Beispiel zeigt wie wir mit mir einer Zeile eine ganze menge Elemente bearbeiten können, die ganze Arbeit erledigt jQuery für uns im Hintergrund. Hier habe ich jQuerys methode "css" benutzt, diese erlaubt es uns eine bestimmte CSS-Eigenschaft eines Elements zu setzten oder zu bearbeiten.
Das ist eigentlich schon das Grundkonzept von jQuery, man wählt sich die Elemente aus mit denen man etwas anstellen will und führt einige Modifikationen an ihnen durch.
Weiterhin bietet uns jQuery auch die Möglichkeit Events anzulegen und zu behandeln, was für uns in Hinsicht auf unsere Aufgaben gleich noch wichtig wird. Aber zunächst ein kleines Beispiel:
$('.my_button').click(function () {
$('.my_button').toggleClass('some_css_class');
});
Dieser Dreizeiler hat es schon ganz schön in sich, denn hier geschieht eine ganze Menge. Als erstes suchen wir uns das Element mit der Klasse my_button und fügen diesem mit der Click-Methode ein Click-Event hinzu. Als Parameter übergeben wir hier auch gleich die Methode, die ausgeführt werden soll, wenn der Benutzer auf dieses Element klickt.
Innerhalb der Methode wählen wir dann dieses Element erneut aus und ändern dessen Klasse mit der toggleClass Methode. Diese Methode hat aber eine Sonderheit, nämlich dass sie sich merkt welche Klasse diesem Element zugewiesen war bevor darauf geklickt wurde, so wird bei einem erneuten Klick die Klasse wieder zurückgetauscht zu der ersten Klasse.
Das ändern der Klassen von Elementen ist sehr wichtig, denn so kann man sehr einfach das Aussehen von diesen ändern. Wenn der Benutzer seinen Namen eingegeben hat soll er Feedback bekommen ob dieser Name ok ist, das soll so realisiert werden:

- Ablauf der Visualisierung
Eingabe -> Überprüfung (Auf dem Server) -> Anzeige des Ergebnisses. Dank der kleinen Icons im Textfeld weiß der Benutzer sofort wie es um seinen Namen steht und er kann auch erkennen, dass die Webseite gerade beschäftigt ist durch den kleinen Kreisel in Schritt 2. Und falls es nicht klappt weiß der Benutzer sofort woran es gelegen hat, da er eine genaue Fehlermeldung bekommt. Und das alles geschieht ohne die Seite neuladen zu müssen nebenbei.
Das Nachladen der benötigten Informationen im Hintergrund nennt man Ajax, dabei ist das keine neue Technologie, sondern einfach nur eine Art einen Request im Hintergrund über JavaScript abzusetzen.
Da wir oben bereits unser Formular angefertigt haben, über das der Benutzer sich registrieren soll können wir uns nun etwas um die Serverseite kümmern und erstellen die nötigen Actions in dem Validation Controller der für die Überprüfung der Vom Benutzer eingegebenen Werte zuständig sein wird:
public class ValidationController : Controller
{
//
// GET: /Validation/CheckUsername/[name]
public String CheckUsername(string username)
{
//Wenn in unserer "Datenbank" der Name bei einem anderen Benutzer Auftaucht
if (new UserDatabase().Users.Where(u => u.Username.Equals(username)).ToList().Count > 0) {
//Eine Fehlermeldung zurückgeben
return "Dieser Benutzername ist schon vergeben";
}
//Wenn der Benutzername kürzer als 5 Zeichen ist ebenfalls Fehler ausgeben
if(username.Length < 5){
return "Der Benutzername ist zu Kurz!";
}
//Falls es nichts zu meckern gibt geben wir "ok" aus
return "ok";
}
}
In diesem Controller habe ich eine Action Methode definiert, die einen string zurück gibt statt einem ActionResult, dadurch wird der Text den wir im Returnstatement abgeben direkt an den Browser weitergereicht ohne Umweg über eine View die wir uns dadurch sparen.
Die Methode selbst macht nichts spannendes, zu erst wird hier die UserDatabase nach Usern durchsucht die den selben Namen haben und bei einem Treffer eine Fehlermeldung ausgegeben und als zweites führe ich eine Prüfung darauf durch ob der Benutzername zu kurz ist. - Falls alle Kriterien passen geben wir ein "ok" zurück.
Die UserDatabase Klasse ist übrigens nur ein Dummy Repository, dass für uns eine Datenbank simuliert:
public class UserDatabase {
public List Users;
public UserDatabase() {
Users = new List(){
new User(){Username = "Bernd", Useremail = "bernd@gmx.de"},
new User(){Username = "Bill", Useremail = "bill@microsoft.de"}
};
}
}
public class User {
public string Username { get; set; }
public string Useremail { get; set; }
}
Nun haben wir eine sehr simple Überprüfung des Benutzernamen eingebaut. Machen wir das gleich noch mal für die Email Adresse und das Passwort:
public string CheckEmail(string email) {
//Wenn in unserer "Datenbank" die Emailadresse bei einem anderen Benutzer Auftaucht
if (new UserDatabase().Users.Where(u => u.Useremail.Equals(email)).ToList().Count > 0) {
//Eine Fehlermeldung zurückgeben
return "Diese Emialadresse ist schon vergeben";
}
return "ok";
}
public string CheckPassword(string password) {
//Unser einziges Kriterium für das Passwort ist dass es mindestens 4 Zeichen lang sein muss
if (password.Length < 4) {
return "Das Passwort ist zu kurz!";
}
return "ok";
}
Absolut nichts kompliziertes wie ihr seht. Natürlich halte ich hier die Methoden und Aktionen sehr simpel um dies verständlicher zu machen, in einem echten Projekt würde man hier natürlich anders vorgehen. Das werden wir dann im zweiten Teil besprechen.
Da unser Backend nun bereit ist müssen wir uns jQuery kümmern und unserem Formular beibringen im Hintergrund mit dem Server zu kommunizieren.
$(document).ready(function () {
$('#UserName').change(function () {
//Diese Aktion wird ausgeführt wenn sich der Test im Username
//Textfeld ändert
//Style klassen entfernen und die validierungsklasse einfügen
$('#UserName').removeClass('validation_ok validation_error');
$('#UserName').addClass('validating');
var username = $('#UserName').val();
$.ajax({ url: '/Validation/CheckUserName/' + username,
success: function (data) {
if (data == "ok") {
//Die Daten wurden vom Controller akzeptiert
$('#UserName').removeClass('validating');
$('#UserName').addClass('validation_ok');
$('#UserName_error').html('');
} else {
//Fehlermeldung vom Server ausgeben
$('#UserName').removeClass('validating');
$('#UserName').addClass('validation_error');
$('#UserName_error').html(data);
}
}
});
});
});
Gehen wir den Code mal gemeinsam durch, wir beginnen mit der jQuery Funktion "ready", diese wird automatisch ausgeführt sobald der Browser die komplette Seite geladen hat. Als Parameter übergeben wir eine anonyme Funktion, die daraufhin direkt ausgeführt wird.
Innerhalb dieser Funktion (Zeile 3) benutzen wir die jQuery Selektoren um die Textbox mit der id "UserName" zu schnappen und hängen dieser ein "change" Event an. Dieses Event wird immer ausgeführt wenn sich der Inhalt der Textbox ändert. An dieser Stelle wollen wir eingreifen und die geänderten Daten zum Server senden, damit er diese prüfen kann.
In Zeile 8 und 9 benutzen wir die "removeClass" und "addClass" Funktionen von jQuery um der "UserName" Textbox CSS Klassen hinzuzufügen oder wegzunehmen. So fügen wir in Zeile 9 die CSS Klasse hinzu, durch die die TextBox das Animations-GIF erhält, das dem Benutzer anzeigt, dass die Eingaben überprüft werden.
Der Spannende Teil beginnt erst in Zeile 11, denn hier lesen wir den Benutzernamen aus der Textbox und senden diesen dann über die "ajax" Methode von jQuery zum Server. Diese Methode von jQuery ist sehr mächtig und man kann hier dementsprechend diverse Parameter definieren, aber wir benutzen hier stattdessen nur die einfachste Variante, und diese benötigt nur die URL an die der Request gesendet werden soll und eine Referenz auf eine Funktion die ausgeführt werden soll wenn alles erfolgreich verlaufen ist.
Die URL selbst haben wir durch die Namen unserer Controller im Backend schon vergeben, denn wir sprechen hier den Validation-Controller an und rufen die CheckUserName-Methode auf, der wir den eingelesenen Namen als Parameter übergeben. Heraus kommt eine URL wie z.B. diese hier: localhost/Validation/CheckUserName/Bill.
Nachdem der Request nun angesendet wurde und erfolgreich eine Antwort empfangen wurde, wird unsere anonyme Funktion ausgeführt, die das vom Server zurückgegebene Ergebnis auswertet. Im Fall dass der Server ein "ok" zurückgibt wird ein grüner Haken im Textfeld angezeigt und ansonsten ein rotes "x" und die Fehlermeldung in nebenstehenden span ausgegeben. (Zeile 23)
Schauen wir uns doch mal an ob alles so funktioniert wie geplant:

- Notiz des Autors: Bei dieser Abbildung wurde kein Wert auf optische Schönheit gelegt
Wie man sieht wurde der Benutzername "Bernd" als fehlerhaft markiert, da dieser bereits in unserer "Datenbank" auftaucht, die Emailadresse hingegen ist ok, da sie noch nicht benutzt wird.
Die jQuery Implementierung für die restlichen Textfelder kann eigentlich 1 zu 1 übernommen werden, es sollten lediglich die Parameter ausgetauscht werden. Die einzige kleine Besonderheit stellt die Prüfung des Passworts dar, ob die beiden Felder gleich sind, das kann man nämlich komplett clientseitig regeln, ohne die Daten zum Server zu senden:
$('#UserPass2').change(function () {
//Diese Aktion wird ausgeführt wenn sich der Text im Passwort
//Textfeld ändert
//Style klassen entfernen
$('#UserPass2').removeClass('validation_ok validation_error');
var pass = $('#UserPass').val();
var pass2 = $('#UserPass2').val();
if (pass == pass2) {
//Die Daten wurden vom Controller akzeptiert
$('#UserPass2').addClass('validation_ok');
$('#UserPass2_error').html('');
} else {
//Fehlermeldung ausgeben
$('#UserPass2').addClass('validation_error');
$('#UserPass2_error').html('Die Passwörter sind nicht gleich');
}
});
Im Grunde sind wir nun fertig und haben auf einfachste Weise unser Registrierungsformular aufgewertet und dem User vielleicht ein paar Nerven gespart.
Ich möchte hier noch mal hinweisen, dass man in einem Realprojekt nicht so handeln würde, da es viel zu umständlich und nicht wirklich gut wartbar ist. Dieser Beitrag soll lediglich die Grundlagen erklären und zeigen wie man so etwas zu Fuß und mit den grundlegendsten Mitteln erstellen kann.
Im nächsten Teil werden wir dann die MVC3 eigenen Features benutzen die uns diese Aufgaben um einiges erleichtern und zu einem Großteil automatisch funktionieren.
Das Demo-Projekt könnt ihr hier laden: Download [VS2010 c#]












