BigBastis Blog About Me & my Digital Lifestyle

17Sep/110

Nice2Know: Attribut-basierende Routen in ASP.NET MVC

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

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

Installation

Installation über NuGet

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

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


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

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


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

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

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

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

Das Ergebnis

Das Ergebnis

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


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

            return View();
        }
    }

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

Läuft!

Läuft!

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

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

11Sep/114

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

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

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

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

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

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

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

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

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

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

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


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

Weitere Informationen

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

Wie komme ich nun an diese Informationen?

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


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

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

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


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

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

Auslesen der Wetterinformationen

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

Die API ist unter folgender URL zu erreichen:

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

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

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

Moment, wie komme ich nun an diese Daten?

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

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

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

Dies kann man z.B so implementieren:


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

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

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

        string response = reader.ReadToEnd();

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

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

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

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

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


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

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

Das Ganze wird von dem ApiController aufgerufen:


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

        public JsonResult GetWeather(string id)
        {

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

            return Json(res, JsonRequestBehavior.AllowGet);
        }

    }

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


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

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

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


function get_weather(address) {

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

}

Sonst noch was?

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

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

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

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

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


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

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

        string response = reader.ReadToEnd();

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

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

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

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

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

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

kick it on dotnet-kicks.de

8Aug/110

Anonyme Threaduebergreifende Eventhandler

Jeder von uns uns hatte schon mit Events und den dazugehörigen Handlern zu tun. Jeder Button auf einem Formular hat z.B. einen eigenen Eventhandler, der irgendwo in der Form.Designer.cs definiert und registriert wird. Dieser sieht dann meist ca so aus:


//Registrierung
this.button1.Click += new System.EventHandler(this.button1_Click);
//Verweis / Nutzung
private void button1_Click(object sender, EventArgs e) { }

So wird jedes Mal wenn des Click-Event der Button-Instanz ausgelöst wird die registrierte Methode button1_Click Ausgeführt. Eigentlich ein schönes und einfaches System, aber wenn man eigene Klassen hat und teilweise viele Instanzen davon bildet ist es unschön, da dabei sehr viele Methoden in der Klasse entstehen und somit  die Übersicht mit der Zeit verloren geht.

Deswegen gibt es für uns die Möglichkeit so genannte anonyme Methoden als EventHandler zu verwenden. Diese benötigen keine Deklaration und haben keine Referenz, somit gehen diese auch verloren, sobald diese zu Ende gelaufen sind. Benutzen kann man diese so:


this.button1.Click += delegate(object sender, EventArgs e) {
    //hier steht mein Code
};

Wie man sieht ist der Code um einiges übersichtlicher geworden und kürzer geworden. Wir übergeben nun eine anonyme Methode als Handler.

Was das ganze noch etwas interessanter macht, ist dass es noch mehr Ersparnis und noch mehr Übersicht bei Threadübergreifenden EventHandlern gibt. Schauen wir uns mal an wie es normalerweise ausschaut:


//Registrierung des EventHandler zum anderen Thread
man.NewInformationUpdate += new NewInformationUpdateArrived(update_received);

//Handlermethode
public void update_received(object sender, TestInformationUpdateEventArgs ev) {
    //Aufruf zu dem eigenen Thread deligieren
    this.BeginInvoke(new delegateupdate(update), new object[] { ev });
}

//Definition der delegaten Methode
public delegate void delegateupdate(TestInformationUpdateEventArgs ev);

//Eigentliche Methode zum Verarbeiten des Events
private void update(TestInformationUpdateEventArgs msg) {
//...
}

Wir haben nun drei Methoden definiert, nur um ein Event zu behandeln, dies ist von Nöten, da wir hier Events verarbeiten, die aus einem anderen Thread kommen.

Hier können wir ebenfalls viel einsparen wenn wir mit delegates und anonymen Methoden arbeiten:


fd1.CompletionInformation += delegate(object se, CompletionInformationArgs ci) {
    this.BeginInvoke(new EventHandler(delegate(object obj, EventArgs ea) {
        CompletionInformationArgs cea = (CompletionInformationArgs)ea;

        //Unser verabeitungs Code hier

    }), new object[] { se, ci });
};

Hier haben wir wieder aus drei Methoden eine gemacht, naja eine richtige zumindest ;) und zwei anonyme. Ich finde diese Variante deutlich lesbarer und übersichtlicher, da alle wichtigen Information an einer Stelle sind.

Doch Vorsicht, diese Methode hat einen gravierenden Nachteil, da wir hier anonyme Methoden benutzen haben wir keine Referenz darauf und können somit das registrierte Event nie wieder un-registrieren. Mit anderen Worten, man muss abwegen wann man diese Abkürzung gehen kann und wann nicht.

Meiner Erfahrung nach wird der Fall überwiegen wo man diesen Weg leider nicht gehen kann, aber dann kann man versuchen ein Zwischenweg zu finden:


    var handler = new EventHandler(delegate(object o, EventArgs e)
    {
        //handler Logik
    };

    Subject.NewEvent += handler;

    //Diesen können wir später dann auch entfernen
    Subject.NewEvent -= handler;

Auf diese Weise haben wir immernoch eine Ersparnis und behalten die volle Kontrolle über unsere Handler.

Als kleine (unschöne) Alternative können wir auch anonyme Handler loswerden, indem wir alle Handler entfernen:


        public event EventHandler SomeEvent;

        public void ClearEventHandlers() {
            Delegate[] delegates = SomeEvent.GetInvocationList();
            foreach (Delegate delegate in delegates) {
                SomeEvent -= (EventHandler) delegate;
            }
        }

4Aug/110

Visual Studio 2010 und das Intellisense im Vergleich

Da ich Beruflich auch viel im Javaumfeld arbeite, komme ich in Kontakt mit verschiedenen IDEs. Am besten gefällt hier das kostenlose NetBeans von Sun Oracle. Und bereits nach kurzer Zeit sieht man die vielen Unterschiede der IDEs, besonders fällt hier aber der Unterschied des Code Completion Features der IDEs auf.

Im Visual Studio haben wir Intellisense kennen- und lieben gelernt, es ist schnell und liefert uns die Informationen die wir haben wollen. Doch schaut man hier mal auf das andere Ufer, wird man schnell feststellen, dass hier doch tatsächlich noch Nachholbedarf besteht.

Schauen wir uns beispielsweise ein typischen Intellisense Pupup an, der uns Informationen zu der Methode Create() der HttpWebRequest-Klasse liefert:

Intellisense Pupup

Intellisense Pupup

Wir bekommen eine Auflistung der uns zur Verfüfung stehenden Methoden (1) und die markierte Methode wirft noch ein weiteres Popup auf, welches uns den Methodenkopf, eine Kurze Beschreibung und die möglichen Exceptions liefert (2).

Eigentlich doch alles was manbraucht, stimmt! Vergleichen wir das Ganze doch mal mit dem "Intellisense" von NetBeans 7:

NetBeans Popup

NetBeans Popup

Was haben wir hier, als erstes taucht, wie im Visual Studio auch, eine Liste mit dem möglichen Befehlen auf (1), diese ist hier etwas großzügiger gestaltet und nimmt etwas mehr Platz auf dem Bildschirm ein, liefert uns aber auch auf den ersten Blick den Return-Typ der Funktion (das was rechtsbündig in der Liste steht) - Im VS müsste man erst die Methode auswählen und auf den "Hilfspopup" warten um an diese Information zu kommen.

Als zweites taucht auch hier ein weiteres Popup auf, mit Details zur Funktion an sich (2). Und das ist der Teil wo es interessant wird, denn hier bekommen viel mehr Informationen als im VS.
Als erstes fällt auf, dass es viel mehr zu lesen gibt, hier wird nämlich das dazugehörige Javadoc geladen und angezeigt, dieses bietet natürlich eine Fülle an Informationen und sogar Beispielcode für die gerade gewählte Funktion.

Um diese Informationen im VS zu erhalten müssen wir die Funktion markieren und dann [F1] drücken, damit sich dann der Browser öffnet und uns zu dem passenden Artikel auf MSDN navigiert, wo wir dann alle nötigen Infos inkl. Beispiele finden, aber warum der Umweg?

Als nächstes fällt auf, dass das in dem zweiten Popup auch Links auftauchen (3), diese kann man natürlich auch anklicken und man wird innerhalb des kleinen Fensters zu dem geklickten Dokueintrag weitergeleitet - das ist wirklich sehr sehr praktisch! Dazu kommt noch oben eine kleine Navigation (4) um zwischen den besuchten Seiten zu wählen, den Artikel im Browser zu öffnen oder direkt zu der gewählten Klasse zu wechseln.

Das macht wirklich Spaß mit dieser Hilfe zu arbeiten und man kann schnell die nötigen Informationen nachschlagen ohne überhaupt die Maus bewegen zu müssen.

Natürlich bin ich mit dessen bewusst, dass es auch für das VS Erweiterungen gibt wie ReSharper oder CodeRush, die sich dieser "Schwäche" annehmen und versuchen es zu verbessern, diese kosten aber erstens Geld und machen aber auch nicht alles perfekt.

Aber es gibt auch Sachen die mich umgekehrt an NetBeans extrem stören, wie zb. die Tatsache, dass NetBeans sich nicht mehrkt welchen Befehl ich als letztes benutzt habe.

Sehr sehr nervig!

Wenn wir das obere Bild anschauen, will ich die methode callVRFY() aufrufen und das mehr fach hintereinander. NetBeans wird mir jedes Mal den Vorschlag genau so machen wie wir ihn hier sehen, ich muss also jedes Mal 12x [Pfeil nach unten] drücken um den Eintrag auszuwählen oder den Namen direkt voll ausschreiben.

Visual Studio reagiert hier deutlich intelligenter, denn es merkt sich, dass man diese Methode nun 2x benutzt hat und schlägt beim nächsten Mal wenn man "call" tippt direkt die callVRFY() Methode als erstes vor.

Eine weitere Sache die bei Visual Studio genial ist, ist die Tatsache, dass ich eigentlich den "call"-Teil des Methodennamen überspringen kann und direkt anfangen kann "VRFY" zu tippen, Intellisense würde dennoch erkennen, dass ich die "callVRFY" Methode meine und diese für mich auswählen.

Beispiel:

Visual Studio Popup

Wie man sieht schlägt mit VS hier alle Klassen & Methoden vor die "response" im Namen haben. - Super!

Das sind nun natürlich zwei "Kleinigkeiten" die ich mir hier herausgepickt habe, aber glaubt mir wenn man plötzlich auf diese verzichten muss ist das mehr als nervig!

Zum Schluss kann ich sagen, dass beide IDEs von ein an der lernen können und dass ich, egal mit welcher IDE ich gerade arbeite das ein oder andere Feature aus der anderen IDE vermisse. Bleibt noch zu hoffen, dass die Entwickler sich gegenseitig auf die Finger schauen und diese netten kleinen Features gegenseitig "ausleihen" ;)

Wie steht ihr dazu? Seit ihr mit dem Intellisense glücklich, nutzt ihr Erweiterungen (ReSharper etc..)?

4Jul/1193

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 ;)

20Jun/113

Get My Keys Back 2 ist nun endlich fertig

Vor ungefähr 1 1/2 Jahren habe ich ein kleines Tool namens Get My Keys Back auf meiner Homepage veröffentlich. Die Aufgabe dieses Tools ist recht simpel, es beschafft die Schlüssel zu den installierten Windows und Office Produkten.

Das Tool war sehr erfolgreich wie ich finde, innerhalb von wenigen Monaten wurde es bereits einige tausend Mal heruntergeladen. Nach etwa 6 Monaten habe ich dann einen Downloadzähler vorgeschaltet und der zeigt nun auch schon 6560 Downloads an, also wurde das Programm in seiner Laufbahn sage ich mal rund 10.000 Mal heruntergeladen - das ist ein toller Wert!

Seit einigen monaten bekomme ich aber auch Mails von Nutzern, die sich beschweren, dass ihre Schlüssel nicht ausgelesen werden. Diese Berichte haben sich langsam gehäuft, sodass ich schon seit einiger Zeit vorhatte ein Update herauszubringen.

Die nötigen Daten für das Update (Erfolgs- und Fehlerberichte von Usern) habe ich in Zusammenarbeit mit dem Forum office-loesung.de erarbeitet. An dieser Stelle ein Danke für euer Feedback!

Die Testphase für die Version 2 von Get My Keys Back läuft nun schon ca 2 Monate und das Feedback war durchweg positiv, sodass ich mir nun sicher genug bin es auf die Öffentlichkeit loszulassen :)

Äußerlich hat sich aber nichts geändert (Dazu fehlte mir dann doch die Zeit, vielleicht kommt das noch mit einem weiteren Update), aber innen hat sich viel getan:

  • Der Erkennungs Algorithmus wurde komplett neugeschrieben
  • Einige Bugs wurden entfernt
  • 64 Bit Systeme werden nun voll unterstützt
  • Keine automatischen Updateprüfungen mehr

Besonders der letzte Punkt war vielen Usern sehr wichtig, und im Nachhinein finde ich es auch etwas blöd von mir es überhaupt so geregelt zu haben, dass ein Tool, welches Schlüssel ausließt eine Internetverbindung aufbaut - ich wäre auch misstrauisch geworden ;) - Aber das ist wie gesagt nun kein Problem mehr.

Ich hoffe GMKB2 wird an den Erfolg von Version 1 anknüpfen und euch eure Schlüssel alle wiederbesorgen :)

Hier gehts zum Download

8Jun/110

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:

  1. Welcher Aufbau einer Webanwendung ist der beste
  2. Wie ermittle ich meinen aktuellen Standort
  3. 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

  1. Daten ermitteln
  2. XML erstellen
  3. 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.

  1. 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)
  2. Die Zeitverschiebung in Stunden (Als Basis gilt hier immer die koordinierte Weltzeit)
  3. 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

  1. Die momentane IP-Adresse
  2. Die eingestellte Sprache
  3. Der aktuelle Ort (Stadt) bzw. das Land
  4. Die Region (hier kommen wir später darauf zurück)
  5. 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.

  1. Länder die als Ganzes einer Zeitzone angehören (z.B. Deutschland, Österreich, Schweiz)
  2. Länder die mehreren Zeitzonen angehören (z.B. USA, Russland, Indonesien)
  3. 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;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; " - " &amp;amp; CDate(tempRSS).AddHours(_offsethours).AddMinutes(_offsetminutes).ToLongTimeString

    Me.LabelCurrentTimeDiff.Text = _offsethours.ToString &amp;amp; _
        " h " &amp;amp; _offsetminutes.ToString &amp;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.

kick it on dotnet-kicks.de

6Jun/112

Unterlagen zu meinem Einstieg in TDD Vortrag

Am 06.06.2011 habe ich bei der .NET Online Usergroup ein Vortrag zum Thema TDD für Einsteiger gehalten. Hierbei ging es darum Entwickler die davon noch nichts oder nur wenig gehört haben in die Thematik einzuarbeiten und den Grundgedanken an Hand eines Beispiels nahezulegen.

Da mich schon die ersten Fragen erreicht haben bekommt ihr hier die Unterlagen zu dem Vortrag.

Info

Denkt daran, dass ihr um die Beispiele durchspielen zu können Visual Studio 2010 mindestens in der Version Professional haben müsst, da sonst die Test-Tools nicht integriert sind.

Aufzeichnung

Einführung in TDD mit Sebastian Gross from .NET Online User Group on Vimeo.

Präsentation

Beispielcode

Weitere Unterlagen

Das von mir verwendete Pflichtenheft enthält neben den im Vortrag bearbeiteten Anforderungen auch noch weitere. Ihr könnt es ja mal ausprobieren ob ihr diese nach der vorgestellten Test-First Herangehensweise implementiert bekommt. Auf Wunsch kann ich auch meine Restliche Implementierung zur Verfügung stellen.

Feedback & Fragen

Falls du bei dem Vortrag dabei warst und noch Fragen hast oder einen (Verbesserungs)vorschlag oder Feedback hast kannst du das auch gerne als Kommentar posten.

Bis zum nächsten Mal!

veröffentlicht unter: Allgemein 2 Kommentare
9Mai/110

DotNet Cologne 2011

Am vergangenen Freitag fand zum dritten Mal die DotNet Cologne statt. Dieses Mal habe ich mich auch dazu entschlossen dabei zu sein und hatte noch das Glück in der letzten Woche noch ein Ticket zu ergattern.

Für mich persönlich war es das erste große Event dieser Art, so stieg ich mit gemischten Gefühlen am Freitag-morgen in den Zug. In Köln angekommen habe ich mich mit Mathias Raacke von Neovelop zusammengetan und auf den Weg zum Media Park gemacht.

Bei der Anmeldung gab es ein Namensschildchen und eine Welcome-Tüte mit der Agenda, dem WLAN-Schlüssel und lauter Sponsoren-items für jeden Teilnehmer, dann hieß es auch schon auf ins Getümmel.

Obwohl wir recht früh da waren war der Aufenthaltsraum (wo es auch leckere belegte Brötchen und reichlich Kaffee gab) schon gut gefüllt und alle Sponsoren hatten schon ihre Plätze bezogen.

Nach ein paar kleinen Unterhaltungen mit ein paar Sponsoren ging es auch schon in die erste Vorlesung, ich habe mich hier für den Windows Phone 7: Silverlight != Silverlight Vortrag von Peter Nowak entschieden, doch wie auch bei den restlichen Beiträgen an diesem Tag verging die Zeit ziemlich schnell.

Die Pausen zwischen den Vorträgen wurden genutzt um den Informationoverload aus so manchen Vorträgen zu verarbeiten und für interessante Gespräche mit den anderen Teilnehmern zu führen.

Ein weiterer Vorteil von solchen Veranstaltungen ist, dass man auch mal die Menschen zu den Twitteraccounts trifft, mit denen man sich öfters unterhält. So habe ich zwischendurch einen gehetzten Albert Weinert, Kay Giza , Philip Proplesch und auch kurz den Ilker Cetinkaya getroffen.

Die ganze Veranstaltung hat mir sehr viel Spaß gemacht und schlauer bin ich auch noch geworden! Deswegen kann ich jetzt schon sagen, dass ich auch bei der nächsten DotNet Cologne dabei sein werde!

Bis dahin werde ich noch am DotNet Open Space Süd in Karlsruhe teilnehmen und auch eventuell an der SeeSharp Party am Bodensee, vielleicht sehen wir uns ja dort :)

6Mrz/111

Twitter for iPhone: “Quick-” bzw. “Trends”-Bar entfernen

Falls ihr ein iPhone besitzt und die Twitter for iPhone App nutzt werdet ihr seit dem letzten Update etwas sehr sehr nerviges festgestellt haben, nämlich die neue halbdurchsichtige Leiste über der Timeline.

Diese so genannte Trends-Bar kann man leider nicht über die Einstellungen der App entfernen. Was noch dazu kommt, ist dass das was diese Leiste anzeigt (für mich) überhaupt keinen Sinn ergibt und einfach nur stört!

An dieser Stelle gibt eine gute Nachricht für alle die ihr iPhone gejailbraked haben, denn die können im Cydia Store die Erweiterung namens "Twizzler" installieren und müssen diese nutzlose Leiste nie wieder sehen!

Falls ihr euer iPhone nicht gejailbraked habt, bleibt euch nur die Wahl eine ältere Version der Twitter App zu installieren oder zu einem anderen Twitter Client zu wechseln!

Get Adobe Flash player