Java: SubjectDN eines X509Zertifikates zu OpenSSL kompatibel machen
Java stellt uns zum angenehmen Arbeiten mit X509-Zertifikaten die Klasse java.security.cert.X509Certificate zur Verfügung. Hier können wir uns sehr einfach über die methode .getSubjectDN().getName() den Distinguished Name des Zertifikats besorgen.
Wenn man in der Javawelt bleibt ist das auch alles ok und funktioniert Problemlos. Will man aber andere Programme wie zum Beispiel OpenSSL anbinden, bekommt man hier ein Problem, denn OpenSSL liefert einen anderen SubjectDN String als unsere Java Klasse.
Wobei mit anders meine ich, dass die Reihenfolge der ausgegebenen Parameter anders ist, hier ein Beispiel:
Ausgabe von OpenSSL (über die X509_NAME_oneline Methode):
/C=DE/ST=NRW/L=Bielefeld/O=ORG/OU=AB1/CN=130.122.146.121
Wogegen die Java-Klasse folgenden String liefert (.getSubjectDN().getName()) :
CN=130.122.146.121, OU=AB1, O=ORG, L=Bielefeld, ST=NRW, C=DE
Die Reihenfolge scheint genau umgekehrt zu sein und auch die Trenner sind anders, und wenn man den String nun als Auth-String benutzen will bekommt man ein Problem.
Leider bietet uns Java hier keine Möglichkeit den String anders zu sortieren, man kann zwar über die Methode getSubjectX500Principal.getName(String format) einen RFC als Parameter mitgeben, welcher die Sortierung etwas anpasst, aber teilweise noch komischere Ergebnisse liefert.
Hier bleibt einem nun leider nichts anderes übrig, als selbst einzugreifen und die Sortierung selbst vorzunehmen. Dazu habe ich eine kleine Klasse geschrieben die das für euch erledigt. Diese hat zwei Methoden, eine die einen OpenSSL Kompatiblen String ausgibt und eine bei der ihr die Reihenfolge der Parameter selbst bestimmen könnt:
/**
* Teilt die Informationen aus dem X509Certificate.SubjectDN in die
* einzelnen Bestandteile auf und macht diese einzeln verfügbar
* @author sebastian gross blog.bigbasti.com
*/
public class CertificateDataSeperator {
private String _C = "";
private String _ST = "";
private String _L = "";
private String _O = "";
private String _OU = "";
private String _CN = "";
private String _EMAILADRESS = "";
/**
* Erstellt eine neue instanz
* @param cert Das Zwetifikat aus dem die Daten gelesen werden sollen
* @param seperator Die Zeichenfolge ander die parameter
* getrennt werden sollen, bei Java ist Standard ", "
*/
public CertificateDataSeperator(X509Certificate cert, String seperator){
String [] subjectDNData;
subjectDNData = cert.getSubjectDN().getName().split(seperator);
seperateData(subjectDNData);
}
/**
* Duchläuft das Array und ordnet die Informationen
* @param data Array mit dem SubjectDN String
*/
private void seperateData(String [] data){
for(String s : data){
if((s.toUpperCase()).startsWith("C=")){
_C = s;
}else if((s.toUpperCase()).startsWith("ST=")){
_ST = s;
}else if((s.toUpperCase()).startsWith("L=")){
_L = s;
}else if((s.toUpperCase()).startsWith("O=")){
_O = s;
}else if((s.toUpperCase()).startsWith("OU=")){
_OU = s;
}else if((s.toUpperCase()).startsWith("CN=")){
_CN = s;
}else if((s.toUpperCase()).startsWith("EMAILADRESS=")){
_EMAILADRESS = s;
}
}
}
/**
* Generiert einen angepassten SubjectDN String
* @param params Die gewünschten Werte in der gewünschten Reihenfolge als Array
* @param seperator Die Zeichenfolge, die zum Trennen der Parameter genutzt werden soll
* @return Einen angepassten SubjectDN String
*/
public String getNewDNString(String [] params, String seperator){
StringBuilder sb = new StringBuilder();
for(String s : params){
if((s.toUpperCase()).equals("C")){
sb.append(_C);
}else if((s.toUpperCase()).equals("ST")){
sb.append(_ST);
}else if((s.toUpperCase()).equals("L")){
sb.append(_L);
}else if((s.toUpperCase()).equals("O")){
sb.append(_O);
}else if((s.toUpperCase()).equals("OU")){
sb.append(_OU);
}else if((s.toUpperCase()).equals("CN")){
sb.append(_CN);
}else if((s.toUpperCase()).equals("EMAILADRESS")){
sb.append(_EMAILADRESS);
}
if(!s.equals(params[params.length-1])){
sb.append(seperator);
}
}
return sb.toString();
}
/**
* Erzeugt einen DN String in dem Format, wie ihn auch die
* X509_NAME_oneline Methode von OpenSSL generiert, dies ist
* auch das Format, welches von dem Exim Email Server genutzt wird.
* Das Forman ist wiefolgt: /C=$/ST=$/L=$/O=$/OU=$/CN=$
* @return OpenSSL
*/
public String getOpenSSLCompatibleDNString(){
StringBuilder sb = new StringBuilder();
sb.append("/").append(_C).append("/").append(_ST).append("/").append(_L).append("/")
.append(_O).append("/").append(_OU).append("/").append(_CN);
//EMAILADRESS Ist ein optionales Feld und könnte nicht gesetzt sein
if(_EMAILADRESS != null){
sb.append("/").append(_EMAILADRESS);
}
return sb.toString();
}
public String getC() {
return _C;
}
public String getCN() {
return _CN;
}
public String getEMAILADRESS() {
return _EMAILADRESS;
}
public String getL() {
return _L;
}
public String getO() {
return _O;
}
public String getOU() {
return _OU;
}
public String getST() {
return _ST;
}
}
Mit dieser kleinen Klasse ist es sehr einfach einen OpenSSL kompatiblen SubjectDN zu erhalten:
X509Certificate myCert = session.getClientCertificate(); CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", "); String dn = sep.getOpenSSLCompatibleDNString();
Wenn man selbst die Reihenfolge der parameter bestimmen will kann man das z.B. so machen:
X509Certificate myCert = session.getClientCertificate();
String [] myParams = new String[]{"O", "CN", "L"};
CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", ");
String dn = sep.getNewDNString(myParams, "|");
Nun soll erst O, dann CN und zum Schluss L ausgegeben, jeweils von einem | (pipe) getrennt.
Das Ganze lässt sich natürlich je nach verwendeten optionalen SSL Parametern noch beliebig erweitern, und ist eine einfache und schnelle Methode um den SubjectDN neu zu sortieren.
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=" & _
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 & " - " & CDate(tempRSS).AddHours(_offsethours).AddMinutes(_offsetminutes).ToLongTimeString
Me.LabelCurrentTimeDiff.Text = _offsethours.ToString & _
" h " & _offsetminutes.ToString & " 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.
.NET WebServices anlegen und konsumieren
Letzte Woche haben uns angeschaut, wie man unter Java einen WebService anlegen kann und diesen dann auch verwenden (konsumieren), heute gucken wir wie das Ganze unter .NET funktioniert und legen den selben Service auch hier mal an.
HINWEIS: Ich verwende hier Visual Studio 2010 Ultimate, soweit ich weiß können die C#/VB.NET Express Versionen keine WebServices anlegen, dazu solltet ihr den Visual Web Developer Express herunterladen.
Klickt also auf Datei>Neu> Projekt und Wählt "Web" bzw. "Internet" unter C#. Damit euch das WebService Projekt überhaupt vorgeschlagen wird müsst ihr die verwendete Frameworkversion auf 2.0 stellen (Siehe Bild), wählt dann ASP.NET Webdienstanwendung. Benennt das Projekt nun "USerDB" (genauso wie im Java Beispiel) und klickt auf OK.
Wenn das Projekt angelegt ist, solltet ihr ungefähr folgendes sehen:
Im Grunde haben wir hier bereits einen funktionierenden WebService den wir auch schon ausführen können. Ähnlich wie beim Glassfish unter Java bekommen wir dann eine Testseite angezeigt auf der alle Methoden des Dienstes sichtbar sind und getestet werden können:
Bevor wir hier aber weiter machen implementieren wir erst einmal unsere Methode aus dem Java Beispiel:
Wenn wir den Service erneut ausführen und auf der Testseite unsere Funktion wählen bekommen wir ein ähnliches Bild zu sehen wie unter Java:
Auch hier wird anschließend ein SOAP Request für uns erzeugt und an den Service gesendet, dieser wird dann inklusive dem SOAP Response auf der Ergebnisseite angezeigt.
Damit haben wir den WebService bereits fertiggestellt, machen wir uns nun also daran diesen zu konsumieren.
Fügen wir als erstes ein neues Konsolen Projekt zu unserer Projektmappe hinzu (Bild 6). Nachdem das Projekt angelegt ist fügen wir einen neuen Webverweis hinzu (Bild 7). In dem Dialog der nun auftaucht kann man die Quelle für die Schnittstellenbeschreibung (WSDL) wählen. Man kann hier direkt eine Referenz auf das Projekt in der Projektmappe anlegen. Wir wollen aber direkt die URL der WSDL in der Obere Textfeld angeben.
An die WSDL kommt man übrigens sehr einfach, indem man die Testerseite des WebServices aufruft und dort auf den Link namens "Dienstbeschreibung" klickt, diese URL müssen wir hier angeben. Das Tool analysiert die WSDL und zeigt uns eine Übersicht über die Operationen dieses Dienstes an (Bild 8), hier tragen wir noch den gewünschten Namen ein (bei mir "UserDBService") und klicken auf Verweis hinzufügen.
An dieser Stelle hat die IDE für uns den harten Teil schon erledigt und alle nötigen Klassen und Methoden generiert, die nötig sind um unseren WebService zu nutzen. Implementieren wir also unsere Funktion aus dem letzten Artikel:
Die interessanten Stellen habe ich hier markiert, so sollte man nicht vergessen eine Referenz auf unseren WebService zu setzen bevor man loslegt.
Im Vergleich zu Java bekommen wir hier sogar noch mehr fürs Geld, denn in den generierten Klasse finden wir zu unseren Methoden auch schon asynchrone Aufrufe inklusive der Completed-Events:
Und da die Klasse von System.Web.Services.Protocols.SoapHttpClientProtocol erbt, bekommen wir auch alle anderen wichtigen Properties und Funktionen die man in einem web-bezogenen Projekt benötigen könnte
Die generierten Klassen und auch die WSDL findet ihr übrigens im Projektverzeichnis in dem Ordner "Web References".
Führen wir nun unser Programm aus, erwartet uns das Selbe wie in unserem Java Beispiel:
Natürlich muss der Dienst nicht unbedingt in .NET sein, so könntet ihr genauso gut die WSDL aus unserem Java Beispiel verwenden um einen .NET Consumer für einen Java WebService zu erstellen .
Java: Webservice anlegen und konsumieren
Aus gegebenen Anlass gibt es heute mal ein kleines Tutorial wie man in Java ein WebService anlegt und diesen dann benutzt. Das ganze wird mit NetBeans 9.6.1 als IDE und einem Glassfish als Application Server realisiert.
Erstmal kurz: was ist ein WebService?
Ein WebService unter Java ist (vergleichbar mit RMI) eine Art Dienst, der sowohl von anderen Anwendungen als auch von Webseiten genutzt werden kann. Anders als eine gewöhnliche Webseite besitzt ein WebService kein eigenes Benutzerinterface sondern stellt nur in einer Schnittstellenbeschreibung seine Dienste nach außen zur Verfügung.
Diese Schnittstellenbeschreibungen werden in WSDL Dateien (Web Service Description Language) gespeichert, diese sind XML Dokumente, die genau beschreiben, welche Methoden der WebService bietet und wie diese angesprochen werden. Glücklicherweise müssen wir diese WSDL Dateien nicht selbst erstellen sondern können uns diese generieren lassen, aber dazu gleich mehr.
Kommunikation mit dem WebService
Unser WebService wird dann später über das SOAP Protokoll (Simple Object Access Protocol) kommunizieren, dieses ist ebenfalls XML basierend und auch hier müssen wir nicht selbst Hand anlegen sondern lassen alles für uns generieren
In diesem Beispiel werden wir einen einfachen WebService erstellen und eine gewöhnliche App, die dann mit dem WebService kommunizieren wird. Wir werden hier die NetBeans IDE 6.9.1 benutzen, diese wird uns hierbei viel Arbeit abnehmen, sodass der unangenehme Teil der Arbeit klein gehalten wird
Downloads - falls nicht bereits vorhanden
NetBeans - Download
Glassfish - Download
JDK - Download
Legen wir mal los
Als Erstes sollten wir damit starten den WebService zu erstellen. Dazu legen wir ein neues Web Application Projekt an und nennes es "CheckUser" die Restlichen Dialoge könnt ihr unverändert lassen und schließlich auf Fertigstellen klicken.
Nun sollte das Projekt angelegt sein und ihr solltet den Quelltext der index.jsp vor euch haben. Diese Datei benötigen wir nicht da wir einen WebService erstellen wollen, deswegen könnt ihr diese Löschen, wenn ihr mögt.
Nun müssen wir einen neuen WebService anlegen, das machen wir, indem wir auf unser Projekt rechtsklicken und über "New -> WebService" wählen. Nennt den Service "UserDB" und packt es in das "com.bigbasti.db.user.service" Package rein. Zum Schluss macht ihr noch den Haken bei "Implement Web Service as Stateless Session Bean" rein und klickt auf Fertig.
Was ist nun passiert? NetBeans hat für uns nun einen neuen Ordner im Projekt namens "Web Services" angelegt und auch eine neue Enterprise Bean die so heißt wie unser Service ist im Projekt aufgetaucht. NetBeans kümmert sich größtenteils selbst um diese automatisch generierten Beans so sollten wir erst einmal die Finger davon lassen.
Nun solltet ihr die UserDB.java vor euch aufgeklappt sehen, diese Klasse ist mit der Annotation Stateless und WebService markiert. Da ein WebService mindestens eine Methode (Operation besitzen muss wird euch wahrscheinlich ein Fehler angezeigt. Das wollen wir nun ändern und fügen dem Service eine neue Operation hinzu.
Macht dazu einen Rechtsklick innerhalb der UserDB Klasse und klickt auf "Insert Code..." wählt hier nun "Add Web Service Operation...". In dem nun angezeigten Fenster solltet ihr eure Operation "checkUserName" nennen und den Rückgabetyp auf Boolean stellen. Desweiteren fügt ihr einen Parameter hinzu und tauft ihn "userName".
Bestätigt das Fenster mit "OK" und ihr solltet folgendes zu sehen bekommen:
Diese neue Methode ist mit "WebMethod" annotiert und weist so darauf hin, dass auf diese von außen zugegriffen werden darf. Alle Parameter sind ebenfalls annotiert sodass diese zugreifbar sind. Diese Annotation ist wichtig, damit wir später WSDLs und Requests generieren können. Beim Arbeiten mit dieser Methode könnt ihr diese aber für euch ausblenden
Diese erzeugte Methode funktioniert genauso wie jede andere Java Methode, somit könnt ihr mit ihr wie gewohnt arbeiten. Diese Methode soll für uns prüfen, ob ein Benutzername bereits von jemandem verwendet wird. Da wir das simel halten wollen nutzen wir eine LinkedList als Datenbank
So können wir eine einfache Prüffunktion implementieren:
Nun haben wir einen WebService mit einer Operation. Diese wollen wir nun mal testen. Dazu verwenden wir zunächst die Test-Tools vom Glassfish Server. Dazu müssen wir unsere Web Application zunächst Deployen (Veröffentlichen), das machen wir ganz einfach indem wir das Projekt rechts-klicken und dann "Deploy" wählen. Nun wird der Glassfish Server gestartet, die Anwendung kompiliert und auf dem Server veröffentlicht.
Wenn dieser Vorgang erfolgreich war könnt ihr im "Services"-Tab unter "Servers->GlassFish Server 3" euere Web App sehen (Siehe Bild 11). Nun wollen wir mal gucken ob unsere Operation funktioniert, dazu macht ihr einen Rechtsklick auf dem "GlassFish Server 3" und wählt View Admin Console (Bild 12).
Nun könnt ihr euch einen Kaffee holen...
Wenn die Admin Console irgendwann offen ist, und es kann dauern, klickt ihr auf "Anwendungen" und wählt eure "CheckUser" App. In dem "Allgemein"-Tab seht ihr nun unten eine Tabelle mit allen Komponenten eurer App, sucht hier unseren Service "UserDB" und klickt auf "Endpunkt anzeigen".
Hier seht ihr nun alle wichtigen Informationen zu eurem Web Service, für uns ist im Moment die Tester URL wichtig (Roter Pfeil) Diese öffnen wir mal in einem neuen Tab.
Was wir hier sehen ist die Testseite für unseren Dienst. Hier hat der Glassfish für jede von uns erstellte Operation (momentan haben wir nur eine) eine Testmethode erstellt. Diese Testmethode macht nichts anderes, als eine SOAP Nachricht zu generieren und an unseren Dienst zu senden und uns die Antwort von diesem anzuzeigen.
Probieren wir das Ganze mal aus, probiert einfach mal ein paar Namen durch und verwendet auch ab und zu einen der von uns Reservierten Namen, ihr werdet feststellen, dass unsere Methode super funktioniert.
Dabei zeigt das Test-Tool auch an welche SOAP Requests es generiert und welche Antworten unser Service gibt, so wird folgender SOAP Aufruf getätigt um zu prüfen, ob der Name vorhanden ist:
Hier finden wir alle unsere Werte aus der Klasse wieder. Der Name der Operation die wir mit diesem Request aufrufen wollen (Rot), diese Operation benötigt einen Parameter namens "userName" den wir hier angeben (Blau) und der Wert für diesen Parameter (Orange). Das Ganze ist dann noch in einen SOAP Envelope verpackt und wird so zum Server geschickt.
Dieser führt die gewünschte Operation aus und gibt uns eine Antwort:
Die Response ist weitestgehend genauso aufgebaut die der Request. Der Knoten hat nun noch "Response" an den Namen der Methode/Operation angehängt bekommen (Rot) und auch das Ergebnis der Operation ist angegeben (Blau).
Das Ergebnis können wir nun mit Hilfe eines Parsers auslesen, aber dazu gleich mehr. Nach dem Test wissen wir nun, dass unser dienst läuft und auch dass die Operation zuverlässig arbeitet, Zeit unsere "Consumer"-Applikation zu bauen.
Web Service benutzen (konsumieren)
Nun haben wir also einen Web Service laufen, und natürlich wollen wir diesen nun auch nutzen (konsumieren ist das Fachwort), aus einer App mit der sich Benutzer irgendwo registrieren können. Diese App muss natürlich irgendwie prüfen, ob der gewünschte Benutzername schon vorhanden (bzw. bereits benutzt) ist, das wird die App über unseren Web Service UserDB erledigen.
Legen wir zunächst ein neues Projekt in NetBeans an, diesmal eine normale Java Application, die wir "RegisterUser" nennen.
Nun müssen wir unserer frischen Applikation beibringen mit einem WebService zusammen zuarbeiten. Das macht man über so genannte Schnittstellen, denn diese beschreiben genau was möglich ist und wie man da dran kommt. Schnittstellen haben hier das Format WSDL, und wenn ihr gut aufgepasst habt habt ihr sicher schon gesehen dass der GlassFish für unser Service eine WSDL erstellt hat.
Geht die Schritte in den Bildern 13,14 und 15 durch (falls ihr den Tab schon geschlossen habt), sodass ihr wieder die URLs des Services angezeigt bekommt. Der Blaue Pfeil im Bild 15 zeigt euch die URL der WSDL, diesen link klickt ihr bitte an, sodass ihr die XML Datei zu Gesicht bekommt.
Auf den Inhalt werde ich hier nicht weiter eingehen, das könnt ihr euch ja bei Wiki durchlesen
Die URL zu der WSDL sieht bei mir wie folgt aus:
http://localhost:8080/UserDBService/UserDB?wsdl
Diese könnt ihr mal in die Zwischenablage kopieren, da wir diese gleich benötigen um einen Web Service Client zu erstellen. Das machen wir mit einem Rechtsklick auf das Projekt und wählen "New->Web Service Client". Hier wählt ihr als Bezugsort für die WSDL "URL" aus und fügt die URL ein. Als Paketnamen habe ich "com.bigbasti.user.register" gewählt.
NetBeans lädt nun die WSDL herunter und erzeugt daraus für euch die nötigen Klassen für den Zugriff auf diese Schnittstelle, deswegen sind einige neue Dateien bei euch im Projektordner aufgetaucht (Bild 24). Der etwas ausgegraute Ordner "Generated Sources" enthält hierbei die wichtigen Klassen und Methoden für das Erzeugen von SOAP Requests und für das Lesen von Responses. Diese Dateien solltet ihr auch möglichst nicht anfassen.
Nun sind wir komplett gerüstet um unseren Web Service zu nutzen. Der Code der dafür geschrieben werden muss ist auch nicht weltbewegend:
Als Erstes sind die imports wichtig, damit wir Zugriff auf die generierten Klassen haben. Mit denen können wir nun (Blaue Pfeile) die nötigen Instanzen der Service Objekte anlegen. Ist das erst geschafft können wir mit unserem UserDB Objekt, das den Service repräsentiert bequem arbeiten und die Service Operationen aufrufen, als ob es eine gewöhnliche kleine Klasse wäre (Oranger Pfeil).
Nachdem wir unsere Operation aufgerufen haben bekommen wir eine Antwort ob der Name schon benutzt wird oder nicht. So könnte es aussehen wenn man das obige Programm startet:
Wie man sieht funktioniert das Ganze sehr gut und es hat gerade mal ein paar Minuten gedauert diesen Service zu erstellen. Ihr könnt natürlich selber weiter herumspielen und weitere komplexere Operationen hinzufügen, die mit echten Datenbanken kommunizieren.
Unbegrenzte Anzahl an Parametern uebergeben
Mir ist aufgefallen, dass viele gar nicht wissen, dass man Methoden so gestalten kann, dass diese eine unbestimmte menge an Parametern akzeptieren. Das gibt es sowohl in Java als auch in .NET (und wohl jeder anderen höheren Programmiersprache)
Wie sieht das Ganze aus? Naja wenn ihr schon mit C#/.NET Gearbeitet habt werdet ihr wohl nicht um diese Funktion herum gekommen sein:
string var = String.Format("Hallo mein Name ist {0} und ich komme aus {1}", "Bernd", "Kassel");
Die Zahlen in den Geschweiften Klammern sind hierbei Platzhalter, die später über eine Variable gefüllt werden. Von diesen Platzhaltern kann man beliebig viele angeben, dementsprechend müssen dann aber auch genauso viele Parameter mit den Werten angegeben werden.
Wie sieht hier die Deklaration dieser Methode aus?
public void DoSomething(int someValue, params string[] values)
{
foreach (string value in values){
Console.WriteLine(value);
}
}
Diese Methode nimmt nun einen normalen int Parameter an und ab dann können beliebig viele Werte vom Typ String folgen:
DoSomething(12345, "Hallo", "Wer", "Bist", "Du");
Genauso kann man das auch in Java machen, hier ist es sogar etwas schöner gelöst wie ich finde:
//Methoden Deklaration
public DoSomething(int someValue, String... values) {
for(String value : values){
System.out.println(value);
}
}
//Nutzung:
DoSomething(12345, "Hallo", "Wer", "Bist", "Du");
In Java werden hier nach dem Variablentyp drei Punkte angehängt die signalisieren, dass dieser Typ nun beliebig oft folgen kann.
Dabei wird intern aber auch nur ein Array mit den Werten erzeugt, das kann man bei der .NET Variante bereits in der Deklaration erkennen. So lässt sich in Java die main-Methode auch so schreiben, ohne dass es zu Problemen kommt:
public static void main(String... args) {
for(String s : args){
System.out.println(s);
}
}
Es sei an dieser Stelle noch erwähnt, dass der Parameter, der beliebig oft folgen kann immer als letztes angegeben werden muss - aus offensichtlichen Gründen
Diese Schreibweise kann an einigen Stellen für deutlich bessere Lesbarkeit sorgen und vereinfacht einfach nur die Anwendung der Funktionen, sollte man auf jeden Fall schon mal gesehen haben.
Fehler: Web Services testen unter NetBeans 6.9.1 (mit Glassfish 3)
Gestern habe ich ziemlich viel Zeit damit verbracht einen ziemlich nervigen Bug in NetBeans 9.6.1 zu umgehen, der auftritt, wenn man WebServices testen möchte.
Wenn ihr einen eigenen WebService anlegt wie zum beispiel in diesem NetBeans Tutorial beschrieben und diesen anschließend mit den Glassfish-eigenen Testern testen wollt werdet ihr diesen Fehler hier sehen:

Service Testen schlägt fehl
Wie es sich nun herausstellte machen wir als User keinen Fehler, sondern NetBeans, denn NetBeans generiert für uns eine Falsche URL, die er dann dementsprechend nicht aufrufen kann und uns diesen Fehler anzeigt.
Interessant an dieser Stelle ist auch, dass dieser Fehler nur in Verbindung mit Stateless WebServices auftritt. Denn wenn man die @Stateless Annotation aus dem Code entfernt scheint es doch zu gehen.
Da man aber im Normalfall alle Services stateless sind, müssen wir uns die passende URL selber basteln. Die korrekte URL ist nämlich so aufgebaut:
http://localhost:8080/[ServiceName]/[ServiceKlasse]?Tester
in meinem obigen Fall wäre es dannhttp://localhost:8080/myServiceService/myService?Tester
Die korrekte URL findet ihr übrigens auch auf der Glassfish Konsole im Bereich Anwendungen->[Anwendung]->[Euer WebService]->Endpunkt Anzeigen - hier stehen alle wichtigen URLs (WSDL, Tester, Service)
Das ist natürlich etwas blöd, da diese Praktische Funktion von NetBeans die sehr einfach mit nur einem Rechtsklick erreichbar ist nicht funktioniert, aber gut was solls, schreiben wir die URLs halt selbst
Dieses Problem tritt bei der 7.0 Beta von NetBeans übrigens nicht mehr auf.
Eine eigene Boolean.ToInt Methode bauen
Lächerlich werden nun sicher alle von euch denken, eine ToInt Methode implementieren, die nichts anderes macht als den boolischen Wert in eine 1 umzuwandeln wenn true oder in eine 0 wenn false.
Im Grunde möchte ich euch hier auch zustimmen, denn das ist alles andere als schwer! Aber C# bietet uns hier viele Möglichkeiten um an unser Ziel zu kommen, deswegen habe ich mir gedacht schreibe ich es mal nieder.
Der Einfachste Ansatz ist sicherlich wie folgt:
public int ToInt(bool wert) {
if (wert) {
return 1;
} else {
return 0;
}
}
Diese Funktion arbeitet natürlich tadellos und erfüllt ihren Zweck zu 100%. Aber es geht noch schöner und eleganter wie ich finde. So kann man sich die IF-Bedingung sparen und das Ganze etwas abkürzen:
public int ToInt(bool wert) {
return Convert.ToInt32(wert);
}
Sehr schön, mehrere Zeilen gespart und das Ergebnis ist immer noch das Selbe! Doch das geht noch weiter, man kann sogar hier noch was einsparen, in dem man die tollen Features von C# nutzt:
public int ToInt(bool wert) {
return wert ? 1 : 0;
}
Falls euch die Syntax nichts sagt, wir nutzen hier den "Conditional Operator", der übrigens schon sehr lange existiert. Zu lesen wäre diese Zeile wie die IF-Bedingung aus dem ernsten Versuch. Wenn die Bedingung vor dem Fragezeichen erfüllt (true) ist dann soll die 1 zurückgegeben werden ansonsten die 0.
Ich finde diese Syntax lässt sich sehr gut lesen, und da ein Boolischer Wert nicht null sein kann laufen wir hier auch nicht Gefahr eine Exception auszulösen. Wenn wir das Ganze noch in eine Extension Method packen können wir das auch sehr bequem benutzen:
public static class Class1 {
public static int ToInt(this bool wert) {
return wert ? 1 : 0;
}
public static bool ToBool(this int wert) {
return wert == 1 ? true : false;
}
public static void main() {
bool var = true;
if (var.ToInt() == 1) {
//...
}
int var2 = 1;
bool var3 = var2.ToBool();
}
}
Ich habe hier auch den umgekehrten Weg mit aufgenommen (IntToBool), hier sollte man vielleicht noch erwähnen, dass nur bei einer 1 true zurückgegeben wird, ansonsten wird false abgeliefert egal welchen int Wert man übergibt!
Wie gesagt, das ist nichts Weltbewegendes, aber ich bin nicht gleich drauf gekommen den Conditional Operator hier zu verwenden daher der Blogpost und vielleicht bringt es ja den einen oder anderen von euch auf tolle Gedanken
Java: New oder nicht New?
Heute gibt es wieder mal nach langer Zeit ein Java Thema. Dieses Thema ist auf den ersten Blick sehr trivial und man schreibt das bestimmt vielfach am Tag ohne drüber nachzudenken.
Konkret gehts heute um return-Statements und auf welche Art diese einen Wert zurück geben. Ein Beispiel soll dies verdeutlichen, nehmen wir an wir haben eine toString() Methode. Diese kann man auf 2 Arten implementieren:
public String toString(){
return "hallo";
}
public String toString2(){
return new String("hallo");
}
Der Unterschied liegt auf der Hand, in der ersten Methode geben wir direkt eine Zeichenfolge zurück und in der zweiten erzeugen wir erstmal ein Objekt von Typ String und geben das dann zurück. Funktioniert beides, aber was benutzt du, und warum?
Denn so unscheinbar das auch aussieht gibt es hier Unterschiede, denn je nachdem welche der beiden Funktionen wir ausführen bekommen wir etwas anderes, ein Beispiel:
String t1 = toString(); String t2 = toString(); String b1 = toString2(); String b2 = toString2(); System.out.println(t1.equals(t2)); //true System.out.println((t1 == t2)); //true System.out.println(b1.equals(b2)); //true System.out.println((b1 == b2)); //false System.out.println(b1.equals(t1)); //true System.out.println((b1 == t1)); //false
Interessant sind hier immer jeweils die zweiten Ausgabezeilen, denn diese sagen, dass wir den == Operator nicht benutzen dürfen und bekommen somit unterschiedliche Referenzen auf verschiedene Objekte zurück.
Nur nicht im ersten Fall, wo wir das Ergebnis ohne new zurück geben, dort können wir den == Operator verwenden - demnach bekommen wir immer exakt die gleiche Referenz zurück egal wie oft wir diese Methode aufrufen, aber wie kann das sein?
Die Antwort liegt wieder in den Grundlagen
denn auch in der ersten Methode geben wir ein Objekt vom Typ String zurück. Dieses Objekt wird erzeugt wenn die Klasse gestartet wird und ist ab dann Klasseninventar, was unter anderem heißt dass es exakt nur ein einziges Mal vorhanden ist.
Daher geben alle Abfragen dieses Strings immer die eine Referenz auf diesen Klassenstring zurück, was dafür sorgt dass wir dem == Operator hier anwenden dürfen und auch folgendes funktioniert:
String a = "hallo"; String b = "hallo"; System.out.println(a == b); //true
Da Strings in Java ebenfalls immutable ist können diese ihren Inhalt nicht ändern, womit die Referenz immer die selbe sein wird.
Warum nun das Ganze? Ich wollte einfach auf diesen kleinen aber doch wichtigen Unterschied hinweisen, da es da zu Fehlern kommen kann wenn man Ergebnisse vergleichen will und man das nicht weiß.
C#: Threads mit Parametern Starten
Wenn ihr auch mal mit Threads gearbeitet habt habt ihr sicherlich auch schon festgestellt, dass es zu Problemen kommt, wenn man einen Thread starten möchte und diesem auch noch Parameter unterjubeln will.
Ein normaler(einfacher) Threadstart sieht ja folgendermaßen aus:
public class test {
public test() {
Auto a = new Auto();
Thread t = new Thread(new ThreadStart(a.Fahre));
}
}
public class Auto {
public void Fahre(){
//...
}
}
Aber was ist wenn wir nun dem Auto auch sagen sollen wie weit es fahren soll? Also so:
public class test {
public test() {
Auto a = new Auto();
Thread t = new Thread(new ThreadStart(a.Fahre(100)));
}
}
public class Auto {
public void Fahre(){
//...
}
public void Fahre(int wieWeit) {
//...
}
}
Leider geht das nicht so einfach, da ThreadStart einen Methodennamen erwartet. Hier müssen wir also anders ansetzen. Eine Möglichkeit dieses Problem zu lösen bzw. zu umgehen ist es die Klasse Auto etwas zu modifizieren und die Parameter, die wir eigentlich über den Methodenaufruf übergeben wollten über den Konstruktor zu übergeben (oder über Properties zu setzen). Somit wäre die Fahre Methode wieder frei von Parametern und könnte problemlos über ThreadStart aufgerufen werden:
public class test {
public test() {
Auto a = new Auto(100);
Thread t = new Thread(new ThreadStart(a.Fahre));
}
}
public class Auto {
private int wieWeit;
public Auto(int wieWeit) {
this.wieWeit = wieWeit;
}
public void Fahre(){
//... hier kann man mit dem Wert
// von wieWeit arbeiten
}
}
Aber diese "Lösung" ist natürlich auch nicht das Gelbe vom Ei. Deswegen habe ich noch ein wenig gegoogelt und habe eine sehr schöne und zugleich einfache Lösung entdeckt:
public class test {
public test() {
Auto a = new Auto();
ThreadStart start = delegate { a.Fahre(100); };
Thread t = new Thread(new ThreadStart(start));
}
}
public class Auto {
public void Fahre(int wieWeit) {
//...
}
}
Hier machen wir von den Anonymen Methoden Gebrauch (siehe Zeile 5) und packen die in ein Delegate, das wir dann dem ThreadStart übergeben. Hier können wir auch bequem unseren Parameter mit angeben und müssen somit auch nicht an der Klasse selbst Änderungen vornehmen.
Kleiner Nachtrag noch:
Maximilian hat mich grad noch an das ParameterizedThreadStart Delegate aufmerksam gemacht, das habe ich noch unbewusst ausgelassen
. Dieses bietet ebenfalls eine angenehme Möglichkeit Parameter an Threads zu übergeben, hier auch dazu ein kleines Beispiel:
public class test {
public test() {
Auto a = new Auto();
Thread t = new Thread(new ParameterizedThreadStart(a.Fahre));
t.Start(100);
}
}
public class Auto {
public void Fahre(object wieWeit) {
//...
}
}
Man beachte hierbei, dass der Typ des Parameters nun Object ist (Zeile 12) deswegen muss vor der Verwendung noch ein Cast stattfinden. Außerdem könnte man Probleme bekommen, wenn man Überladungen benutzen will.
Das ganze ist natürlich nichts weltbewegendes, aber da ich es noch nicht kannte habe ich es hier mal mit aufgenommen.
ASP.NET MVC3: Was gibts neues?
Einige von euch haben es sicher mitbekommen, dass vor Kurzem der Release Candidate (RC) von ASP.NET MVC3 veröffentlich wurde. Download, Changelog. Diese neue Version bringt einige für uns Entwickler tolle neue Funktionen und Vereinfachungen mit.
Die wohl auffälligste Neuerung ist die neue ViewEngine namens Razor, diese sorgt dafür, dass die Views um einiges schlanker und lesbarer werden durch die Einführung einer neuen Syntax.
Ich will mit euch ein kleines Beispielprojekt erstellen in dem wir Schritt für Schritt einige der Neuerungen durchgehen.
Fangen wir an und erstellen ein neues MVC3 Projekt in Visual Studio und bereits hier gibts es was Neues zu betrachten:

Bild 1: Der Neue Projekt erstellen Dialog
Da wir von Vorn beginnen möchten erstellen wir ein neues Leeres Projekt und wählen die neue Razor ViewEngine.
Layout
Wenn man nun einen Blick auf den Projektmappenexplorer wirft, wird man drin nicht viel finden, lediglich eine CSS- und zwei cshtml-Dateien. Die cshtml-Dateien fangen auch noch mit einem Unterstrich (_) an und haben irgendwas mit Layout im Namen.
Was ist das?
Öffnen wir zunächst im Shared-Ordner die Datei _Layout.cshtml.
Diese Datei kann man mit einer MasterPage aus MVC2 vergleichen. Wenn ihr genau hinschaut werdet ihr im Title-Tag einen Platzhalter finden und auch im body-div ist einer namens „@RenderBody()“.
Die Layoutseiten in MVC3 funktionieren anders als die in MVC2, auch wenn diese Seite auch Platzhalter besitzt die später mit Inhalt befüllt werden fällt dennoch die Methode RenderBody() auf. Diese Methode ist in jeder Layout-Seite drin und steht immer an der Stelle wo später der Inhalt der Seite stehen wird. Neben der RenderBody()-Methode gibt es auch die RenderSection()-methode, die auch einen Namen bekommen und auch als optional markiert werden können.
Was auch neu ist, ist dass wir keine <% %> Tags mehr haben in die wir den ASP.NET Code schrieben können, das alles übernimmt nun das @-Zeichen. Das System ist dabei so intelligent, dass es erkennt bis wohin der Code geht weswegen man auch kein schließen Tag benötigt. Weitere Informationen zu der Razor Syntax.
Wie man sieht sind die ganzen JavaScript Verweise auskommentiert. Bitte entfernt die Kommentarmarkierung, da wir diese Skripte bald benötigen.
Gut, schleißen wir diese Datei und werfen nun einen Blick in die „_ViewStat.cshtml“ in unserem Views-Ordner:
Der Inhalt hier ist wie man sieht sehr übersichtlich ![]()
Was macht diese Datei nun? Wie man sieht verweist diese auf die _Layout.cshtml die wir eben betrachtet haben, dabei sorgt dieser Verweis dafür, dass alle Views in dem Views-Ordner inkl Unterordner diesen Verweis auch erhalten. Das hat zur Folge, dass man nun nicht mehr in jeder einzelnen View diese Referenz auf die Layoutdatei eintragen muss und spart sich so den redundanten Code.
Viel mehr gibt es dazu eigentlich auch nicht zu sagen. Gehen wir nun zum nächsten Schritt und erstellen eine neue View.
Views
Wenn ihr den View-Hinzufügen Dialog wählt werdet ihr auch hier eine Veränderung feststellen:
Wie man sieht kann man nun auch bei jeder einzelnen View wählen, welche ViewEngine man hier gern verwenden möchte. Legen wir eine gewöhnliche View an.
Die neue View enthält nun nur diese paar Zeilen Code (siehe Bild oben).
Zuerst wird ein Codeblock aufgemacht mit der @{ … } Syntax, das bedeutet, dass nun nur noch ASP.NET Code kommt bis die schließen Klammer kommt, auch über mehrere Zeilen.
Innerhalb dieses Codeblocks füllen wir nun den Titel unserer Seite, wenn ihr euch erinnern könnt wurde diese Variable in der MasterPage im Title-Tag wieder ausgegeben.
Man beachte hier, dass diese Property dynamisch erstellt wird und es die Property „Title“ eigentlich gar nicht gibt. Diese wird dann zur Laufzeit generiert und mit Inhalt befüllt. So kann man beliebig viele solcher Properties anlegen.
In Zeile 3 geben wir an welche MasterPage hier verwendet werden soll, da wir aber bereits die Datei „_ViewStat.cshtml“ in unserem Views-Ordner liegen haben müssen wir innerhalb der View keinen Verweis mehr auf eine Masterpage setzen. (Außer wir wollen eine andere Nutzen) Also können wir diese Zeile aus unserem Code löschen.
Nun fängt schon ab Zeile 6 der normale HTML Quelltext an, ohne dass wir irgendwo einen Zugehörigkeitsbereich definiert haben. Das ist ein weiteres MVC3 / Razor Feature, denn alles wird an der stelle in der MasterPage eingesetzt wo wir die Methode RenderBody() aufrufen. Eine Ausnahme besteht, wenn wir Sektionen in der Masterpage definiert haben, diese würden dann dementsprechend der passenden Sektion zugeordnet werden.
So könnte eine Sektion aussehen:
Das wars nun soweit zu dieser View. Wenn ihr mögt könnt ihr nun weiter damit arbeiten wie mit einer gewöhnlichen View.
Nun wollen wir mal ein Formular anlegen, dass wir zum Registrieren eines Benutzers nutzen wollen. Dazu benötigen wir zunächst ein Model auf das wir dann unsere View mit der Form typisieren.
Legen wir also eine neue Klasse im Models-Verzeichnis an. Die Model Klassen verhalten sich in MVC3 eigentlich genauso wie in MVC2, nun sind aber einige neue Attribute hinzugekommen die ich euch hier mal vorstellen möchte:
Wie ihr seht hat meine Modelklasse 5 Eigenschaften. Die ersten drei sind mit dem Attribut [Required] markiert und sind somit Pflichtfelder. Alle Eigenschaften besitzen ein [DisplayName()] Attribut, dass den Text enthält, der später in der View angezeigt werden soll.
Kommen wir nun zu den neuen Attributen. Erstmal das [Required, Compare(…)] Attribut. Dieses Attribut sorgt hier dafür, dass „RepeatEmail“ mit „Email“ verglichen wird und übereinstimmen muss damit die Validierung erfolgreich ist. So müssen wir diese zwei Werte nun nicht mehr im Controller manuell vergleichen.
Weiter unten bei der About-Eigenschaft finden wir ein weiteres neues Attribut namens [SkipRequestValidation] – Im Prinzip sagt der Name schon alles aus. Dieses Attribut sorgt dafür, dass diese Property beim Binding keine Exception wirft wenn dort HTML enthalten ist.
Früher musste man dann im Controller die Action mit einem [ValidateInput(false)] markieren, was natürlich immer noch funktioniert, aber nun kann man auch einzelne Properties bequem markieren ohne gleich für die ganze Methode den Validationinput abzuschalten.
Hier gibts es nun sogar die Möglichkeit bestimmte Properties von der Validierung auszuschließen:
Hier werden die Properties “Body” und “Summary” nicht auf HTML Inhalt überprüft.
Wie ihr sehen könnt habe ich ein Attribut auskommentiert. Auf dieses Attribut habe ich mich persönlich sehr gefreut, aber es scheint nicht zu funktionieren (oder ich mache etwas falsch, wenn ihr meinen Fehler seht bitte kommentieren ACHTUNG: dazu habe ich hier ein Update geschrieben)
Dieses RemoteAttribut erlaubt es eine Eigenschaft an eine Methode in einem Controller zu binden und währen der Ausführung der View beim Client im Hintergrund über Ajax aufzurufen. Für den Fall von der Property Name würde die Methode UserNameAvailabla im Controller Register aufgerufen werden, welche ein boolean zurück gibt. So wird dann dynamisch eine Fehlermeldung auf der View erscheinen.
Wenn man dieses RemoteAttribut nun benutzen möchte muss man natürlich die oben angegebene Methode im angegebenen Controller implementieren, zB so:
Diese Methode wird dann beim Validieren per Ajax aufgerufen und zeigt dann dementsprechend auch eine Fehlermeldung an (Falls nötig):
Zum Schluss möchte ich noch darauf hinweisen, dass ich ein bestimmtes kleines Feature vermisst habe, nämlich eine bequeme Möglichkeit einen String auszugeben ohne diesen mit HTML zu Encoden. Denn soweit bietet Razor leider keine Möglichkeit dafür. Deswegen muss man hier mit einer eigenen kleinen Extension Methode aushelfen.
Das wäre es dann nun auch für heute. Ich habe ein kleines Beispiel Projekt vorbereitet, dass ihr hier herunterladen könnt. (.NET 4.0 VS2010)




































