Georg fährt extra nach Berlin um Steve Ballmer zu treffen

31 Tage Mango | Tag #14: OData verwenden

Die­ser Arti­kel ist Tag #14 der Serie 31 Tage Man­go von Jeff Blan­ken­burg.

Der Ori­gi­nal­ar­ti­kel befin­det sich hier: Day #14: Using ODa­ta.

Die­ser Arti­kel wur­de in der Ori­gi­nal­se­rie von Gast­au­tor Chris Woo­d­ruff geschrie­ben. Bei Twit­ter kann Chris unter @cwoodruff erreicht wer­den. Chris ver­öf­fent­licht außer­dem den Pod­cast Deep Fried Bytes.

(Anm. leit­ning: Der Ori­gi­nal­ar­ti­kel hat lei­der eini­ge inhalt­li­che Schwä­chen — viel­leicht bzw. hof­fent­lich wird das noch mal über­ar­bei­tet. Ich bin des­halb teil­wei­se stark vom Ori­gi­nal­ar­ti­kel abge­wi­chen, habe Code­bei­spie­le her­um­ge­scho­ben und gan­ze Absät­ze dazu­ge­dich­tet. Beson­ders glück­lich bin ich mit dem Ergeb­nis immer noch nicht. Zum Umgang mit ODa­ta — und zum Sinn und Ein­satz von View­Mo­dels — emp­feh­le ich Ihnen auf jeden Fall die Lek­tü­re wei­te­rer Quel­len.)

Heu­te wol­len wir uns anschau­en, wie man Daten aus einem ODa­ta Feed in einer Anwen­dung ver­wen­det und wie eine Win­dows Pho­ne Anwen­dung die­se Daten ver­wen­den kann.

Was ist OData?

Offi­zi­ell ist das Open Data Pro­to­col (ODa­ta) ein Web-Pro­to­koll um Daten abzu­fra­gen und zu aktua­li­sie­ren. So wie wir es von SQL Daten­ban­ken seit Jah­ren gewohnt sind, kön­nen wir mit die­sem Pro­to­koll Selec­ts auf Daten durch­füh­ren, und die­se löschen, aktua­li­sie­ren und spei­chern. Ein Vor­teil für uns Ent­wick­ler von Win­dows Pho­ne Man­go Apps ist, dass die Ein­bin­dung eines ODa­ta Feeds durch die von Micro­soft zur Ver­fü­gung gestell­ten Biblio­the­ken sehr ein­fach ist. Die­se Biblio­the­ken nut­zen dabei die im ODa­ta Stan­dard defi­nier­ten Meta­da­ten eines ODa­ta Feeds.

Hin­ter den Kulis­sen schi­cken wir ODa­ta Anfra­gen mit­tels HTTP an den Web­ser­ver, der ein ODa­ta Feed bereit­stellt. Mehr über ODa­ta kön­nen Sie hier erfah­ren.

OData für Ihre Mango Anwendung aufsetzen

Mit dem Win­dows Pho­ne SDK 7.1 ist die Ver­wen­dung von ODa­ta sehr ein­fach gewor­den. In der vor­he­ri­gen Ver­si­on, dem Win­dows Pho­ne SDK 7.0, muss­ten wir ein exter­nes Werk­zeug ver­wen­den um die Pro­xy­klas­sen für ein ODa­ta Feed zu gene­rie­ren. Jetzt gibt es hier­für ein­ge­bau­te Unter­stüt­zung im Visu­al Stu­dio 2010. Um ein ODa­ta Feed ein­zu­bin­den, erstel­len wir zunächst ein neu­es Pro­jekt aus der Vor­la­ge Sil­ver­light for Win­dows Phone/Windows Pho­ne Data­bound App­li­ca­ti­on.

Das Pro­jekt wird stan­dard­mä­ßig fol­gen­den Auf­bau haben.

Um aus der Anwen­dung Zugriff auf das ODa­ta Feed zu bekom­men, fügen wir das Feed über Add Ser­vice Refe­rence im Visu­al Stu­dio 2010 hin­zu.

Im Dia­log Add Ser­vice Refe­rence wer­den Sie um die Anga­be der URL zum ODa­ta Feed gebe­ten. Wenn Sie auf den But­ton Go drü­cken, wird das Visu­al Stu­dio 2010 die Infor­ma­tio­nen von der ange­ge­be­nen URL abfra­gen. Wenn die Ein­ga­ben abge­schlos­sen sind, drü­cken Sie OK und das ODa­ta Feed wird Ihrem Pro­jekt hin­zu­ge­fügt (d.h. es wer­den die Pro­xy­klas­sen aus den ODa­ta Meta­da­ten gene­riert).

In unse­rem Bei­spiel­pro­jekt wer­den wir ein ODa­ta Feed zu his­to­ri­schen Base­ball­sta­tis­ti­ken ver­wen­den.

http://www.baseball-stats.info/OData/baseballstats.svc/

Durch Bestä­ti­gung des Add Ser­vice Refe­rence Dia­logs wird eine Pro­xy­klas­se gene­riert, die es Ihnen erlaubt, mit den Daten aus dem ODa­ta Feed zu arbei­ten. Durch das Hin­zu­fü­gen des ODa­ta Feeds wur­de wei­ter­hin eine Refe­renz auf das Assem­bly System.Data.Services.Client hin­zu­ge­fügt. Die­ses brau­chen wir für die APIs zum Zugriff auf die Daten.

Die Klasse DataServiceCollection

Im Name­space System.Data.Services.Client sind eine Viel­zahl von Klas­sen ent­hal­ten, die uns ermög­li­chen, ein­fach mit dem ODa­ta Feed zu arbei­ten. Für die­sen Arti­kel brau­chen wir nur die Klas­se Data­Ser­vice­Collec­tion. Eine Aus­ein­an­der­set­zung mit den ande­ren Klas­sen die­ses Name­spaces ist aber auf jeden Fall loh­nens­wert.

Die Klas­se Data­Ser­vice­Collec­tion ist die ent­schei­den­de API zur Arbeit mit dem ODa­ta Feed und des­sen Meta­da­ten. Ins­be­son­de­re ermög­licht Ihnen die­se Klas­se, CRUD (Crea­te, Read, Update, Dele­te) auf den Daten durch­zu­füh­ren. In die­sem Arti­kel wer­den wir nur lesen­de Funk­tio­nen der Klas­se Data­Ser­vice­Collec­tion ver­wen­den und die gele­se­nen Daten an die Benut­zer­ober­flä­che in unse­rer Anwen­dung bin­den.

Daten von einem OData Feed abfragen

Wenn Sie eine Anwen­dung von einer Pro­jekt­vor­la­ge neu erstel­len, ent­hält das Pro­jekt bereits eine Datei MainPage.xaml wie im Screen­shot unten. Ich habe gegen­über der Vor­la­ge nur die Eigen­schaf­ten App­li­ca­ti­on­Tit­le und Page­Tit­le im XAML ange­passt.

Um die Klas­se Data­Ser­vice­Collec­tion und unse­re zuvor ange­leg­te Ser­vice Refe­rence zu ver­wen­den, fügen wir die fol­gen­den using State­ments im Kopf der code-behind Datei hin­zu.

using System.Data.Services.Client;
using _31DaysMangoOData.ServiceReference1; 

In der MainPage.xaml.cs dekla­rie­ren wir die fol­gen­den pri­va­ten Varia­blen. Die _context Varia­ble wird uns Zugriff auf das ODa­ta Feed geben und die _selectedTeams Varia­ble wird das aktu­ell gewähl­te Base­ball Team reprä­sen­tie­ren. Von die­sem wer­den wir eine Lis­te der Jah­re, in denen die­ses Team gespielt hat, anzei­gen.

private TeamFranchise _selectedTeam;
private BaseballStatsEntities _context;

Im Hand­ler für den Loa­ded Event unse­rer Main­Page laden wir die eigent­li­chen Daten unse­res ODa­ta 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 wer­den sich viel­leicht fra­gen, war­um man hier die URI zum ODa­ta Feed ange­ben muss, wo wir die­se doch bereits bei der Erstel­lung der Ser­vice Refe­rence ange­ge­ben haben. Im Dia­log zur Erstel­lung der Ser­vice Refe­rence haben wir zwar die URI ange­ge­ben, aber nur, damit das Visu­al Stu­dio aus den Meta­da­ten des ODa­ta Feeds die Pro­xy­klas­sen gene­rie­ren kann. Wenn wir eine Instanz unse­res Dat­a­Con­texts erstel­len, müs­sen wir die URI zum ODa­ta Feed Ser­vice immer ange­ben.

(Anm. leit­ning: lei­der pas­sen ins­be­son­de­re die fol­gen­den beschrei­ben­den Tex­te im Ori­gi­nal­ar­ti­kel über­haupt nicht zu den Code­bei­spie­len. Ich muss­te des­halb an vie­len Stel­len stark vom Ori­gi­nal­ar­ti­kel abwei­chen)

Die Eigen­schaft Teams unse­res View­Mo­dels ist vom gene­ri­schen Typ Data­Ser­vice­Collec­tion mit dem Typ­ar­gu­ment Team­Fran­chise, wel­ches wie­der­um ein Enti­ty Type unse­res ODa­ta Feeds ist.

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 Load­Com­ple­ted die­ser Teams Data­Ser­vice­Collec­tion fan­gen wir mit unse­rer Metho­de Teams_Loaded. Die eigent­li­che Abfra­ge auf den Daten machen wir mit LINQ. Zwei gro­ße Vor­tei­le der Klas­se Data­Ser­vice­Collec­tion sind ers­tens die Mög­lich­keit, die Daten per LINQ abzu­fra­gen und zwei­tens die Mög­lich­keit, die Daten direkt an unse­re XAML Ober­flä­che zu bin­den (damit wer­den wir uns im nächs­ten Abschnitt aus­ein­an­der­set­zen).

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 abge­schlos­sen ist (kei­ne Con­ti­nua­ti­on mehr), set­zen wir den Dat­a­Con­text der Main­Page (genau­er: des äuße­ren Grids in der Main­Page) auf die Teams Collec­tion.

Daten von OData an die Oberfläche binden

Betrach­ten wir nun auf der XAML Sei­te, wie wir die im Main­View­Mo­del ent­hal­te­nen Daten über den Dat­a­Con­text an eine List­Box in der Main­Page und an eine Detail­an­sicht bin­den.

Da wir als Pro­jekt­vor­la­ge eine Win­dows Pho­ne Data­bound App­li­ca­ti­on gewählt haben, wur­de der Dat­a­Con­text im Kon­struk­tur der Main­Page bereits auf das Main­View­Mo­del gesetzt.

DataContext = App.ViewModel;

Da wir in unse­rem Fall kei­ne Design-Time Daten haben, kön­nen wir die fol­gen­de Zei­le aus dem von der Vor­la­ge gene­rier­ten XAML ent­fer­nen:

d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}" 

Wei­ter­hin ändern wir das Bin­ding der Main­List­Box auf ein­fach nur {Bin­ding}, da wir den Dat­a­Con­text direkt auf die Collec­tion von Teams set­zen:

this.LayoutRoot.DataContext = App.ViewModel.Teams;

Das fol­gen­de Code­bei­spiel ent­hält den über­ar­bei­te­ten XAML Code für die Main­Page:

<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 Anwen­dung jetzt star­ten, wer­den Sie sehen, dass die Base­ball­teams in der Anwen­dung ange­zeigt wer­den.

Wie Sie im obi­gen XAML Code­bei­spiel sehen, haben wir für den Selec­tion­Ch­an­ged Event der List­Box eine Event Hand­ler Metho­de MainListBox_SelectionChanged regis­triert (Anm. leit­ning: für die es lei­der kein Code­bei­spiel gibt). Wenn der Anwen­der auf den Namen eines Base­ball­teams drückt, wird die­se Metho­de auf­ge­ru­fen und wir navi­gie­ren zu einer Detail­an­sicht zum ent­spre­chen­den Base­ball­team.

Jetzt bleibt uns noch, das gewähl­te Team auf der DetailPage.xaml anzu­zei­gen. Bei der Navi­ga­ti­on von der Main­Page zur Detail­Pa­ge über­ge­ben wir den Index des selek­tier­ten Ein­trags in der Lis­te als Que­ry String Argu­ment. Da die Lis­te an der Benut­zer­ober­flä­che genau der Collec­tion im Main­View­Mo­del ent­spricht (per Data­bin­ding ver­knüpft), kön­nen wir den Index ver­wen­den, um in der Detail­Pa­ge das Daten­ob­jekt aus der Teams Collec­tion des Main­View­Mo­dels zu erhal­ten. Damit kön­nen wir den Dat­a­Con­text der Detail­Pa­ge auf das Daten­ob­jekt des gewähl­ten Teams set­zen. Der ent­spre­chen­de Code für die Detail­Pa­ge fin­det sich im fol­gen­den Code­bei­spiel.

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 ledig­lich den App­li­ca­ti­on­Tit­le, Page­Tit­le und das Data­bin­ding für die Eigen­schaft franch­Na­me der Team Klas­se geän­dert. Als klei­ne zusätz­li­che Infor­ma­ti­on wird im Text­Block Con­tent­Text der Wert der Eigen­schaft active ange­zeigt.

<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 Anwen­dung jetzt noch ein­mal star­ten und ein Base­ball­team aus­wäh­len um zur Detail­Pa­ge zu navi­gie­ren, wird die­se dann bei­spiels­wei­se so aus­se­hen:

Zusammenfassung

Daten mit dem ODa­ta Pro­to­koll in einer Win­dows Pho­ne Anwen­dung zu ver­wen­den ist sehr ein­fach. In die­sem Arti­kel haben wir nur an der Ober­flä­che gekratzt. Der Name­space System.Data.Services.Client und die Klas­se Data­Ser­vice­Collec­tion bie­ten Ihnen noch vie­le wei­te­re Mög­lich­kei­ten zur Arbeit mit ODa­ta.

Um ein lauf­fä­hi­ges Pro­jekt für das Visu­al Stu­dio 2010 her­un­ter­zu­la­den, drü­cken Sie auf den Down­load Code But­ton.

Mor­gen wird Gast­au­tor Doug Mair Ihnen die Pro­gress­Bar vor­stel­len. Damit kön­nen Sie Ihren Anwen­der über eine lang lau­fen­de Ope­ra­ti­on infor­mie­ren und ihn gleich­zei­tig unter­hal­ten.

Bis dahin!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert