Dieser Artikel ist Tag #14 der Serie 31 Tage Mango von Jeff Blankenburg.
Der Originalartikel befindet sich hier: Day #14: Using OData.
Dieser Artikel wurde in der Originalserie von Gastautor Chris Woodruff geschrieben. Bei Twitter kann Chris unter @cwoodruff erreicht werden. Chris veröffentlicht außerdem den Podcast Deep Fried Bytes.
(Anm. leitning: Der Originalartikel hat leider einige inhaltliche Schwächen — vielleicht bzw. hoffentlich wird das noch mal überarbeitet. Ich bin deshalb teilweise stark vom Originalartikel abgewichen, habe Codebeispiele herumgeschoben und ganze Absätze dazugedichtet. Besonders glücklich bin ich mit dem Ergebnis immer noch nicht. Zum Umgang mit OData — und zum Sinn und Einsatz von ViewModels — empfehle ich Ihnen auf jeden Fall die Lektüre weiterer Quellen.)
Heute wollen wir uns anschauen, wie man Daten aus einem OData Feed in einer Anwendung verwendet und wie eine Windows Phone Anwendung diese Daten verwenden kann.
Was ist OData?
Offiziell ist das Open Data Protocol (OData) ein Web-Protokoll um Daten abzufragen und zu aktualisieren. So wie wir es von SQL Datenbanken seit Jahren gewohnt sind, können wir mit diesem Protokoll Selects auf Daten durchführen, und diese löschen, aktualisieren und speichern. Ein Vorteil für uns Entwickler von Windows Phone Mango Apps ist, dass die Einbindung eines OData Feeds durch die von Microsoft zur Verfügung gestellten Bibliotheken sehr einfach ist. Diese Bibliotheken nutzen dabei die im OData Standard definierten Metadaten eines OData Feeds.
Hinter den Kulissen schicken wir OData Anfragen mittels HTTP an den Webserver, der ein OData Feed bereitstellt. Mehr über OData können Sie hier erfahren.
OData für Ihre Mango Anwendung aufsetzen
Mit dem Windows Phone SDK 7.1 ist die Verwendung von OData sehr einfach geworden. In der vorherigen Version, dem Windows Phone SDK 7.0, mussten wir ein externes Werkzeug verwenden um die Proxyklassen für ein OData Feed zu generieren. Jetzt gibt es hierfür eingebaute Unterstützung im Visual Studio 2010. Um ein OData Feed einzubinden, erstellen wir zunächst ein neues Projekt aus der Vorlage Silverlight for Windows Phone/Windows Phone Databound Application.
Das Projekt wird standardmäßig folgenden Aufbau haben.
Um aus der Anwendung Zugriff auf das OData Feed zu bekommen, fügen wir das Feed über Add Service Reference im Visual Studio 2010 hinzu.
Im Dialog Add Service Reference werden Sie um die Angabe der URL zum OData Feed gebeten. Wenn Sie auf den Button Go drücken, wird das Visual Studio 2010 die Informationen von der angegebenen URL abfragen. Wenn die Eingaben abgeschlossen sind, drücken Sie OK und das OData Feed wird Ihrem Projekt hinzugefügt (d.h. es werden die Proxyklassen aus den OData Metadaten generiert).
In unserem Beispielprojekt werden wir ein OData Feed zu historischen Baseballstatistiken verwenden.
http://www.baseball-stats.info/OData/baseballstats.svc/
Durch Bestätigung des Add Service Reference Dialogs wird eine Proxyklasse generiert, die es Ihnen erlaubt, mit den Daten aus dem OData Feed zu arbeiten. Durch das Hinzufügen des OData Feeds wurde weiterhin eine Referenz auf das Assembly System.Data.Services.Client hinzugefügt. Dieses brauchen wir für die APIs zum Zugriff auf die Daten.
Die Klasse DataServiceCollection
Im Namespace System.Data.Services.Client sind eine Vielzahl von Klassen enthalten, die uns ermöglichen, einfach mit dem OData Feed zu arbeiten. Für diesen Artikel brauchen wir nur die Klasse DataServiceCollection. Eine Auseinandersetzung mit den anderen Klassen dieses Namespaces ist aber auf jeden Fall lohnenswert.
Die Klasse DataServiceCollection ist die entscheidende API zur Arbeit mit dem OData Feed und dessen Metadaten. Insbesondere ermöglicht Ihnen diese Klasse, CRUD (Create, Read, Update, Delete) auf den Daten durchzuführen. In diesem Artikel werden wir nur lesende Funktionen der Klasse DataServiceCollection verwenden und die gelesenen Daten an die Benutzeroberfläche in unserer Anwendung binden.
Daten von einem OData Feed abfragen
Wenn Sie eine Anwendung von einer Projektvorlage neu erstellen, enthält das Projekt bereits eine Datei MainPage.xaml wie im Screenshot unten. Ich habe gegenüber der Vorlage nur die Eigenschaften ApplicationTitle und PageTitle im XAML angepasst.
Um die Klasse DataServiceCollection und unsere zuvor angelegte Service Reference zu verwenden, fügen wir die folgenden using Statements im Kopf der code-behind Datei hinzu.
using System.Data.Services.Client; using _31DaysMangoOData.ServiceReference1;
In der MainPage.xaml.cs deklarieren wir die folgenden privaten Variablen. Die _context Variable wird uns Zugriff auf das OData Feed geben und die _selectedTeams Variable wird das aktuell gewählte Baseball Team repräsentieren. Von diesem werden wir eine Liste der Jahre, in denen dieses Team gespielt hat, anzeigen.
private TeamFranchise _selectedTeam; private BaseballStatsEntities _context;
Im Handler für den Loaded Event unserer MainPage laden wir die eigentlichen Daten unseres OData Feeds.
private void MainPage_Loaded(object sender, RoutedEventArgs e) { _context = new BaseballStatsEntities(new Uri("http://www.baseball-stats.info/OData/baseballstats.svc/")); App.ViewModel.Teams.LoadCompleted += Teams_Loaded; var query = from t in _context.TeamFranchise orderby t.franchName where t.active == "Y" select t; App.ViewModel.Teams.LoadAsync(query); }
Sie werden sich vielleicht fragen, warum man hier die URI zum OData Feed angeben muss, wo wir diese doch bereits bei der Erstellung der Service Reference angegeben haben. Im Dialog zur Erstellung der Service Reference haben wir zwar die URI angegeben, aber nur, damit das Visual Studio aus den Metadaten des OData Feeds die Proxyklassen generieren kann. Wenn wir eine Instanz unseres DataContexts erstellen, müssen wir die URI zum OData Feed Service immer angeben.
(Anm. leitning: leider passen insbesondere die folgenden beschreibenden Texte im Originalartikel überhaupt nicht zu den Codebeispielen. Ich musste deshalb an vielen Stellen stark vom Originalartikel abweichen)
Die Eigenschaft Teams unseres ViewModels ist vom generischen Typ DataServiceCollection
using System; using System.ComponentModel; using System.Data.Services.Client; using _31DaysMangoOData.ServiceReference1; namespace _31DaysMangoOData { public class MainViewModel : INotifyPropertyChanged { public MainViewModel() { this.Teams = new DataServiceCollection<TeamFranchise>(); } /// <summary> /// A collection for ItemViewModel objects. /// </summary> public DataServiceCollection<TeamFranchise> Teams { get; private set; } public bool IsDataLoaded { get; private set; } /// <summary> /// Creates and adds a few ItemViewModel objects into the Items collection. /// </summary> public void LoadData() { this.IsDataLoaded = true; } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (null != handler) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } }
Den Event LoadCompleted dieser Teams DataServiceCollection fangen wir mit unserer Methode Teams_Loaded. Die eigentliche Abfrage auf den Daten machen wir mit LINQ. Zwei große Vorteile der Klasse DataServiceCollection
private void Teams_Loaded(object sender, LoadCompletedEventArgs e) { if (e.Error == null) { if (App.ViewModel.Teams.Continuation != null) { App.ViewModel.Teams.LoadNextPartialSetAsync(); } else { // Set the data context of the list box control to the team data. this.LayoutRoot.DataContext = App.ViewModel.Teams; } } else { MessageBox.Show(string.Format("We have an error: {0}", e.Error.Message)); } }
Wenn das Laden der Daten abgeschlossen ist (keine Continuation mehr), setzen wir den DataContext der MainPage (genauer: des äußeren Grids in der MainPage) auf die Teams Collection.
Daten von OData an die Oberfläche binden
Betrachten wir nun auf der XAML Seite, wie wir die im MainViewModel enthaltenen Daten über den DataContext an eine ListBox in der MainPage und an eine Detailansicht binden.
Da wir als Projektvorlage eine Windows Phone Databound Application gewählt haben, wurde der DataContext im Konstruktur der MainPage bereits auf das MainViewModel gesetzt.
DataContext = App.ViewModel;
Da wir in unserem Fall keine Design-Time Daten haben, können wir die folgende Zeile aus dem von der Vorlage generierten XAML entfernen:
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
Weiterhin ändern wir das Binding der MainListBox auf einfach nur {Binding}, da wir den DataContext direkt auf die Collection von Teams setzen:
this.LayoutRoot.DataContext = App.ViewModel.Teams;
Das folgende Codebeispiel enthält den überarbeiteten XAML Code für die MainPage:
<phone:PhoneApplicationPage x:Class="_31DaysMangoOData.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True"> <!--Data context is set to sample data above and LayoutRoot contains the root grid where all other page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="31 Days of Mango" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="baseball teams" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> <!--ContentPanel contains ListBox and ListBox ItemTemplate. Place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding}" SelectionChanged="MainListBox_SelectionChanged"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="0,0,0,17" Width="432" Height="78"> <TextBlock Text="{Binding Path=franchName}" TextWrapping="NoWrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Grid> </phone:PhoneApplicationPage>
Wenn Sie die Anwendung jetzt starten, werden Sie sehen, dass die Baseballteams in der Anwendung angezeigt werden.
Wie Sie im obigen XAML Codebeispiel sehen, haben wir für den SelectionChanged Event der ListBox eine Event Handler Methode MainListBox_SelectionChanged registriert (Anm. leitning: für die es leider kein Codebeispiel gibt). Wenn der Anwender auf den Namen eines Baseballteams drückt, wird diese Methode aufgerufen und wir navigieren zu einer Detailansicht zum entsprechenden Baseballteam.
Jetzt bleibt uns noch, das gewählte Team auf der DetailPage.xaml anzuzeigen. Bei der Navigation von der MainPage zur DetailPage übergeben wir den Index des selektierten Eintrags in der Liste als Query String Argument. Da die Liste an der Benutzeroberfläche genau der Collection im MainViewModel entspricht (per Databinding verknüpft), können wir den Index verwenden, um in der DetailPage das Datenobjekt aus der Teams Collection des MainViewModels zu erhalten. Damit können wir den DataContext der DetailPage auf das Datenobjekt des gewählten Teams setzen. Der entsprechende Code für die DetailPage findet sich im folgenden Codebeispiel.
using System.Windows.Navigation; using Microsoft.Phone.Controls; namespace _31DaysMangoOData { public partial class DetailsPage : PhoneApplicationPage { // Constructor public DetailsPage() { InitializeComponent(); } // When page is navigated to set data context to selected item in list protected override void OnNavigatedTo(NavigationEventArgs e) { string selectedIndex = ""; if (NavigationContext.QueryString.TryGetValue("selectedItem", out selectedIndex)) { int index = int.Parse(selectedIndex); DataContext = App.ViewModel.Teams[index]; } } } }
Im XAML Code der DetailPage.xaml habe ich lediglich den ApplicationTitle, PageTitle und das Databinding für die Eigenschaft franchName der Team Klasse geändert. Als kleine zusätzliche Information wird im TextBlock ContentText der Wert der Eigenschaft active angezeigt.
<phone:PhoneApplicationPage x:Class="_31DaysMangoOData.DetailsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True"> <!--Data context is set to sample data above and first item in sample data collection below and LayoutRoot contains the root grid where all other page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent" d:DataContext="{Binding Items[0]}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="31 Days of Mango" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="ListTitle" Text="{Binding franchName}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> <!--ContentPanel contains details text. Place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock x:Name="ContentText" Text="{Binding active}" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}"/> </Grid> </Grid> </phone:PhoneApplicationPage>
Wenn Sie die Anwendung jetzt noch einmal starten und ein Baseballteam auswählen um zur DetailPage zu navigieren, wird diese dann beispielsweise so aussehen:
Zusammenfassung
Daten mit dem OData Protokoll in einer Windows Phone Anwendung zu verwenden ist sehr einfach. In diesem Artikel haben wir nur an der Oberfläche gekratzt. Der Namespace System.Data.Services.Client und die Klasse DataServiceCollection bieten Ihnen noch viele weitere Möglichkeiten zur Arbeit mit OData.
Um ein lauffähiges Projekt für das Visual Studio 2010 herunterzuladen, drücken Sie auf den Download Code Button.
Morgen wird Gastautor Doug Mair Ihnen die ProgressBar vorstellen. Damit können Sie Ihren Anwender über eine lang laufende Operation informieren und ihn gleichzeitig unterhalten.
Bis dahin!