Vorwort
Dieses Tutorial ist für Einsteiger gedacht. Um mitarbeiten zu können benötigen Sie Visual Studio 2010 mit dem Silverlight 4 Add On und Vorkenntnisse in C#. Ich zeige Ihnen hier lediglich eine von vielen Lösungen dieses Problems.
Inhalt
- Google Weather API
- Erstellen eines Proxy-Services
- Klassendiagramm
- Erstellen der Klassen
- Erstellen der GUI
Vorab müssen Sie darüber im Klaren sein, dass die API nicht Dokumentiert ist. Schauen wir uns die API genauer an:
http://www.google.com/ig/api?weather=Stadt.Land&hl=De
Wenn wir den Link genauer betrachten sehen wir, dass er drei Parameter besitzt welche wir verändern können. Hier “Stadt”, “Land” und “De”. Man kann allerdings “Land” weglassen und anstelle einer Stadt deren Postleitzahl eingeben. Mit dem “De” am Ende des Links geben wir an in welcher Sprache uns die Wetterdaten übermittelt werden sollen. Wenn wir den Link nach diesen Angaben bearbeiten sieht er z.B. so aus:
http://www.google.com/ig/api?weather=Berlin.germany&hl=De
Hierbei ist es egal ob man das Land in Englisch oder in Deutsch schreibt.
Beim aufrufen dieses Links wird uns eine XML-Struktur angezeigt die wie folgt aussieht:

Es gibt nur drei unterschiedliche Parts die für uns Interessant sind und zwar:

Nun werfen wir einen näheren Blick auf die drei verschiedenen Parts und suchen uns die Attribute raus die für uns wichtig sind.

- Gibt uns Stadt und Bundesland.
- Gibt uns das aktuelle Datum.
- Gibt uns an wie das Wetter voraussichtlich wird.
- Gibt uns die aktuelle Temperatur in Celsius.
- Gibt uns die aktuelle Luftfeuchtigkeit.
- Gibt den Pfad an wo das aktuelle Bild zum Wetter liegt.
- Gibt uns die aktuelle Windstärke und Richtung.
- Gibt uns den Wochentag.
- Gibt uns die Mindesttemperatur.
- Gibt uns die Maximaltemperatur.
Erstellen eines Proxy-Services
Da wir nun die XML-Struktur verstanden und uns die für uns wichtigen Punkte herausgesucht haben, geht es daran die API mit Silverlight zu Konsumieren. Hier tritt das erste Problem auf. Silverlight bekommt keinen Zugriff auf die Google API, da es keine crossdomain.xml und keine clientaccesspolicy.xml findet, die Silverlight die Berechtigung zum Zugriff gibt. Silverlight braucht eine von diesen beiden Dateien. In diesen Dateien gibt der Eigentümer an auf welche Services und Inhalte von wem zugegriffen werden darf und auf welche nicht. Die clientaccesspolicy.xml ist speziell für Silverlight entwickelt worden. Die crossdomain.xml wird von Flash verwendet aber da diese Datei schon ziemlich viel Verwendung im Internet hatte haben sich die Entwickler von Silverlight dafür entschlossen das Silverlight auch nach der crossdomain.xml-Datei sucht. Allerdings nur wenn es keine clientaccesspolicy.xml findet und wenn in der crossdomain.xml der Zugriff von allen Domänen gestattet wird. Um trotzdem mit Silverlight auf die Google Weather API zugreifen zu können, ist ein Proxy-Service notwendig, der sich auf der Download Domäne der Silverlight Applikation befindet und die Anfragen an die API stellt. Einen solchen Service erstellen wir an dieser Stelle.
Zur Verdeutlichung:

Den Proxy Service erstellen Sie, indem Sie zuerst eine Silverlight Applikation mit aktivem WCF-Dienst erstellen. Nun fügen Sie einen WCF Service hinzu, der später auf die Google Weather API zugreifen wird.

Diesen lassen wir nun die Google Weather API ansprechen. Hierzu verwenden wir den WebClient und den StreamReader. Folglich müssen wir den Google Weather API Link als Stream deklarieren.
using System; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Activation; using System.Net; using System.IO; namespace WeatherApp.Web { [ServiceContract(Namespace = "")] [SilverlightFaultBehavior] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class GoogleWeatherApiService { [OperationContract] public string GetGoogleWeatherApiString(string _strCity) { WebClient client = new WebClient(); Stream stream = client.OpenRead("http://www.google.com/ig/api?weather=" + _strCity + "&hl=De"); using (StreamReader streamReader = new StreamReader(stream, System.Text.Encoding.Default)) { return streamReader.ReadToEnd(); } } // Add more operations here and mark them with [OperationContract] } }
In dem Link der Google API befindet sich der String _strCity, da der User später seine Stadt eingeben können soll.
Nun fügen Sie der Silverlight Applikation eine neue Service Referenz hinzu, in dem Sie mit der rechten Maustaste auf Referenzen klicken und dann Service Referenz hinzufügen auswählen. Wenn Sie dies getan haben öffnet sich folgendes Fenster:

Über Ermitteln wird Visual Studio 2010 Ihren WCF-Service automatisch erkennen.
Jetzt ist es an der Zeit sich Gedanken darüber zu machen, wie unsere Applikation funktionieren soll. Ich empfehle hierzu ein Klassendiagramm, welches Sie in Visual Studio 2010 durch Rechtsklick auf die Klasse erstellen können. Wir brauchen drei verschiedene Klassen, eine zum Auslesen der Service Referenz und zwei, denen wir die ausgelesenen Daten übermitteln lassen, damit wir später Listen dieser Klassen anfertigen können. In meinem Fall heißen diese Klassen “Data”, “Current” und “Forecast”.

Welche Methode für was zuständig ist und was es mit dem Event auf sich hat, werden wir uns später anschauen. Wichtig vorab zu wissen ist nur, dass die Variablen alle private deklariert wurden und die Eigenschaften der “Current” und “Forecast” Klassen später die Variablen verändern.
Da wir nun wissen, was unsere Klassen beinhalten sollen erstellen wir diese. Ich habe mit der “Current” Klassen begonnen.
public class Current { private string _strCity; private DateTime _oDate = new DateTime(); private string _strCondition; private int _intTemp; private string _strHumidity; private string _strWindCondition; private string _strImage; }
Nun müssen wir die Variablen Verkapseln, damit wir diese später von außerhalb verändern lassen können. Hierzu klicken sie einfach mit der rechten Maustaste auf die Variable klicken dann auf Umgestalten und wählen Einkapseln aus.

Wenn Sie dies getan haben wird sich ein Fenster öffnen in dem Sie den Namen der Variable angeben sollen:

Wenn Sie nun auf “OK” klicken wird die Verkapselung automatisch von Visual Studio 2010 generiert und sieht nun so aus:
private string _strCity; public string strCity { get { return _strCity; } set { _strCity = value; } }
Dies machen Sie für jede Variable in der Klasse “Current”.
Nun können wir mit der Klasse “Forecast” beginnen. In dieser müssen Sie ebenfalls die Variablen als privat deklarieren und diese dann Verkapseln.
public class Forecast { private string _strDay; private int _intLowTemp; private int _intHighTemp; public int intHighTemp; private string _strCondition; public string strCondition; private string _strImage; public string strImage; }
Wenn Sie mit diesen beiden Klassen fertig sind und jede Variable Verkapselt haben, können Sie mit der Data-Klasse beginnen. Diese wird auf die Service Referenz zugreifen und das vom Proxy-Service abgefragte in einen String einspeichern. Dieser String wird mittels XmlReader ausgelesen und die für uns wichtigen Daten den entsprechenden Klassen übergeben und dann eine Liste der Klasse erstellt.
Zuerst deklarieren wir die für uns wichtigen Variablen und Listen. Hier brauchen wir keinerlei Verkapselung, da auf diese Variablen von anderen Klassen nicht zugegriffen wird.
public class Data { private string _strCity; private string _strDate; private string _strCurrentCondition; private string _strCondition; private string _strCurrentTemp; private string _strHumidity; private string _strWindCondition; private string _strDay; private string _strLowTemp; private string _strHighTemp; private string _strImage; private string _strWeatherData; public List listCurrentList = new List(); public List listForecastList = new List();
Nun werden wir uns darum kümmern, dass das vom WCF-Service an die Referenz übergebene in einen String eingespeichert wird.
public void LoadData(string _strCity) { GoogleWeatherApiServiceClient client = new GoogleWeatherApiServiceClient(); client.GetGoogleWeatherApiStringCompleted += new EventHandler (client_GetGoogleWeatherApiStringCompleted); client.GetGoogleWeatherApiStringAsync(_strCity); } void client_GetGoogleWeatherApiStringCompleted (object sender, GetGoogleWeatherApiStringCompletedEventArgs e) { if (e.Error == null) { _strWeatherData = e.Result; GetData(); FireXMLReaderReady(); } }
Zur “GetData()”-Methode, die hier ausgeführt wird, kommen wir später. Die “FireXMLReaderReady()”-Methode werden wir jetzt implementieren. Die “FireXMLReaderReader()”-Methode entsteht durch ein Event, das wir erstellen müssen, da die Daten Asynchron geladen werden. Durch das eben genannte asynchrone Laden kann es passieren, dass die Daten noch nicht fertig geladen sind, wenn wir auf diese zugreifen wollen. Um dieses Problem zu lösen werden wir ein Event erstellen, das ausgelöst wird, wenn die Daten in den String eingetragen wurden.
public event EventHandler XMLReaderReady; public void FireXMLReaderReady() { if (XMLReaderReady != null) XMLReaderReady(this, EventArgs.Empty); }
Wenn wir dieses Event erstellt haben kommen wir zum Hauptteil, indem wir die für uns wichtigen Daten aus dem String auslesen und an die entsprechenden Klassen übergeben lassen.
private void GetData() { Current OCurrent = new Current(); Forecast OForecast = new Forecast(); using (XmlReader reader = XmlReader.Create(new StringReader(_strWeatherData))) { while (reader.Read()) { if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName != null)) { if (reader.HasAttributes) { if (("icon" == reader.LocalName)) { _strImage = reader.GetAttribute("data"); } if (("city" == reader.LocalName)) { _strCity = TranslateFerderalState( reader.GetAttribute("data")); } if (("forecast_date" == reader.LocalName)) { _strDate = reader.GetAttribute("data"); } if (("condition" == reader.LocalName)) { _strCurrentCondition = reader.GetAttribute("data"); _strCondition = reader.GetAttribute("data"); } if (("temp_c" == reader.LocalName)) { _strCurrentTemp = reader.GetAttribute("data"); } if (("humidity" == reader.LocalName)) { _strHumidity = reader.GetAttribute("data"); } if (("wind_condition" == reader.LocalName)) { _strWindCondition = reader.GetAttribute("data"); } if (("day_of_week" == reader.LocalName)) { _strDay = reader.GetAttribute("data"); } if (("low" == reader.LocalName)) { _strLowTemp = reader.GetAttribute("data"); } if (("high" == reader.LocalName)) { _strHighTemp = reader.GetAttribute("data"); } if(_strCity != null && _strCity!= "" && _strDate != null && _strDate != "" && _strCurrentCondition != null && _strCurrentCondition != "" && _strCurrentTemp != null && _strCurrentTemp != "" && _strHumidity != null && _strHumidity != "" && _strWindCondition != null && _strWindCondition != "" && _strImage != null && _strImage != "") { OCurrent.strCity = _strCity; _strDate = ConvertStringToDate(_strDate); OCurrent.ODate = DateTime.Parse(_strDate); OCurrent.strCondition = _strCurrentCondition; OCurrent.intTemp = Convert.ToInt32(_strCurrentTemp); OCurrent.strHumidity = _strHumidity; OCurrent.strWindCondition = _strWindCondition; _strImage = ConvertToShortPath(_strImage); OCurrent.strImage = _strImage; listCurrentList.Add(OCurrent); OCurrent = new Current(); _strHumidity = ""; //_strCity = ""; _strDate = ""; _strCurrentCondition = ""; _strCondition = ""; _strCurrentTemp = ""; _strWindCondition = ""; } if(_strCondition != null && _strCondition != "" && _strLowTemp != null && _strLowTemp != "" && _strHighTemp != null && _strHighTemp != "" && _strDay != null && _strDay != "" && _strImage != null && _strImage != "") { OForecast.intHighTemp = Convert.ToInt32(_strHighTemp); OForecast.intLowTemp = Convert.ToInt32(_strLowTemp); OForecast.strCondition = _strCondition; OForecast.strDay = _strDay; _strImage = ConvertToShortPath(_strImage); OForecast.strImage = _strImage; listForecastList.Add(OForecast); OForecast = new Forecast(); _strDay = ""; _strHighTemp = ""; _strLowTemp = ""; _strCondition = ""; _strImage = ""; } } } } } }
Hier sehen sie mehrere Prüfungen. Wir lassen prüfen, ob das was wir benötigen, z.B. “City”, vorhanden ist. Ist dies der Fall wird es in unsere lokale Variable eingetragen. Wenn diese Prüfungen alle durchlaufen wurden wird noch geprüft ob alle Daten vorhanden sind, welche die Klassen “Current” und “Forecast” benötigen. Trifft dies zu werden die Daten an die Verkapselten Variablen übergeben und direkt danach wird die entsprechende Klasse in ihre Liste eingetragen. Da nicht alle Daten so Formatiert sind, dass sie für uns sofort brauchbar sind, brauchen wir noch drei Methoden die uns helfen die Daten in das richtige Format umzuwandeln.
Als erstes brauchen wir die Methode “ConvertStringToDate()”, welche uns das übergebene Datum, z.B. 2011-10-24 in ein für uns brauchbares Format bringt z.B. 24.10.2011.
private static string ConvertStringToDate(string _strDate) { string[] arrDateSplitted = _strDate.Split('-'); _strDate = arrDateSplitted[2] + "." + arrDateSplitted[1] + "." + arrDateSplitted[0]; return _strDate; }
Desweiteren brauchen wir die Methode “ConvertToShortPath()”, die uns den von der Google API gegebenen Link für das Bild kürzt. Dies ist notwendig um später schneller auf die Bilder zugreifen zu können.
private static string ConvertToShortPath(string _strImage) { string[] arrPathSplitted = _strImage.Split('/', '.'); _strImage = arrPathSplitted[4]; return _strImage; }
Falls Sie wollen, das die Ausgabe der Städte und Bundesländer in deutsch erfolgt, brauchen Sie noch die Methode “TranslateFerderalState()”.
private static string TranslateFerderalState(string _strCity) { string[] arrCitySplitted = _strCity.Split(','); if (arrCitySplitted[0] == "Munich") { arrCitySplitted[0] = "München"; } else if(arrCitySplitted[0] == "Cologne") { arrCitySplitted[0] = "Köln"; } else if(arrCitySplitted[0] == "Hanover") { arrCitySplitted[0] = "Hannover"; } else if (arrCitySplitted[0] == "Nuremberg") { arrCitySplitted[0] = "Nürnberg"; } if (arrCitySplitted.Length == 2) { if (arrCitySplitted[1] == " Thuringia") { arrCitySplitted[1] = " Thüringen"; } else if (arrCitySplitted[1] == " Saxony") { arrCitySplitted[1] = " Sachsen"; } else if (arrCitySplitted[1] == " Rhineland-Palatinate") { arrCitySplitted[1] = " Rheinland Pfalz"; } else if (arrCitySplitted[1] == " Saxony-Anhalt") { arrCitySplitted[1] = " Sachsen Anhalt"; } else if (arrCitySplitted[1] == " Hesse") { arrCitySplitted[1] = " Hessen"; } else if (arrCitySplitted[1] == " Mecklenburg-West Pommerania") { arrCitySplitted[1] = " Mecklenburg Vorpommern"; } else if (arrCitySplitted[1] == " North Rhine-Westphalia") { arrCitySplitted[1] = " Nordrhein-Westfalen"; } else if (arrCitySplitted[1] == " Lower Saxony") { arrCitySplitted[1] = " Niedersachsen"; } else if (arrCitySplitted[1] == " Bavaria") { arrCitySplitted[1] = " Bayern"; } else { arrCitySplitted[1] = arrCitySplitted[1]; } _strCity = arrCitySplitted[0] + "," + arrCitySplitted[1]; } return _strCity; }
Nach der Implementierung der Logik müssen wir noch die GUI (Grafische Benutzeroberfläche) entwerfen und dafür sorgen, dass die Daten auch korrekt übergeben und angezeigt werden.
Entwerfen wir zuerst die GUI:

Meinen XAML-Code finden Sie in meiner Projektmappe.
Lassen Sie Ihrer Fantasie freien lauf und gestalten Sie die Oberfläche so wie Sie es möchten.
Als Bilder benutze ich die standard Google Weather-Bilder. Um dies ebenfalls zu tun laden sie diese einfach runter und wandeln Sie diese von “.gif” zu “.png” um, da Silverlight “.gif” nicht verwenden kann. Bei mir liegen diese Bilder dann in der Projektmappe in einem separaten Ordner Namens “GoogleWeatherImages”.
Achtung: sollten Sie sich entschließen Ihre GUI anders zu gestalten müssen Sie darauf achten, dass die Übergabe der Daten auf die GUI anders aussehen wird als bei meinem Beispiel.
Bevor wir mit der Übergabe der Daten an die GUI beginnen müssen wir erst einmal dafür sorgen, dass unser Projekt beim Start die “LoadData()”-Methode der Klasse Data ausführt. Daraufhin soll dann das Event “FireXMLReaderReady()” gefeuert werden.
Data OData = new Data(); List listCurrentList = new List(); List listForecastList = new List(); public Weather() { OData.LoadData("Berlin"); InitializeComponent(); OData.XMLReaderReady += new EventHandler(OData_XMLReaderReady); } private string _strImagePath = "/WeatherApp;component/GoogleWeatherImages/"; private string _strDate;
void OData_XMLReaderReady(object sender, EventArgs e) { try { listCurrentList = OData.listCurrentList; listForecastList = OData.listForecastList; BuildApp(); } catch (Exception) { MessageBox.Show("Bitte eine korrekte Stadt eingeben."); } }
private void BuildApp() { _strDate = listCurrentList[0].ODate.ToShortDateString(); _strDate = FormateDateString(_strDate); _lblCityLable.Content = "Wetter in " + listCurrentList[0].strCity + ":"; _lblConditionLabel.Content = listCurrentList[0].strCondition; _lblMinMaxLabel.Content = "Min" + listForecastList[0].intLowTemp + "°C" + "|" + "Max" + listForecastList[0].intHighTemp + "°C"; _lblHumidityLabel.Content = listCurrentList[0].strHumidity; _lblWindConditionLabel.Content = listCurrentList[0].strWindCondition; _lblCurrentTempLabel.Content = "Aktuell: " + listCurrentList[0].intTemp + "°C"; _lblTodayLable.Content = "Heute, der " + _strDate + "(" + listForecastList[0].strDay + ")"; _imgTodayImage.Source = new BitmapImage(new Uri(_strImagePath + listCurrentList[0].strImage + ".png", UriKind.RelativeOrAbsolute)); _lblDay1Label.Content = listForecastList[1].strDay; _lblDay1MinMaxLabel.Content = listForecastList[1].intLowTemp + "°|" + listForecastList[1].intHighTemp + "°"; _imgDay1Image.Source = new BitmapImage(new Uri(_strImagePath + listForecastList[1].strImage + ".png", UriKind.RelativeOrAbsolute)); _lblDay2Label.Content = listForecastList[2].strDay; _lblDay2MinMaxLabel.Content = listForecastList[2].intLowTemp + "°|" + listForecastList[2].intHighTemp + "°"; _imgDay2Image.Source = new BitmapImage(new Uri(_strImagePath + listForecastList[2].strImage + ".png", UriKind.RelativeOrAbsolute)); _lblDay3Label.Content = listForecastList[3].strDay; _lblDay3MinMaxLabel.Content = listForecastList[3].intLowTemp + "°|" + listForecastList[3].intHighTemp + "°"; _imgDay3Image.Source = new BitmapImage(new Uri(_strImagePath + listForecastList[3].strImage + ".png", UriKind.RelativeOrAbsolute)); }
Auch hier benötigen wir wieder eine Hilfsmethode, die das Datum kürzt.
private string FormateDateString(string _strDate) { string[] arrDateSplitted = _strDate.Split('.'); _strDate = arrDateSplitted[0] + "." + arrDateSplitted[1]; return _strDate; }
Wenn Sie nun Ihre Applikation Debuggen ermittelt diese nur für die Stadt das Wetter, die sie beim Start der Applikation mit der “LoadData()”-Methode übergeben haben. Um dem Benutzer die Möglichkeit zu bieten das Wetter einer eingegebenen Stadt zu sehen, müssen wir das Button Click Event implementieren.
private void _btnCalculateWeather_Click(object sender, RoutedEventArgs e) { string _strCity = _txtCity.Text; listCurrentList.Clear(); listForecastList.Clear(); _txtCity.Text = ""; OData.LoadData(_strCity); }
Nun ist die Applikation fertig und sieht wie folgt aus:

Meine Projektmappe können Sie hier herunterladen.
