Consumiendo servicio de datos de Windows Azure utilizando el cliente OData

Este artículo es una traducción del artículo original encontrado en: http://create.msdn.com/en-US/education/quickstarts/ODataService.

 

 

El Protocolo Open Data (OData) se basa en un modelo de entidad y relación que te permite acceder a datos en el estilo de recursos REST.  OData permite el uso del protocolo estándar HTTP para ejecutar consultas, e incluso crear, actualizar y eliminar datos de un servicio de datos remoto.  Mediante el uso de la biblioteca de cliente OData para Windows Phone, tu aplicación de Windows Phone puede consumir datos de un servicio de Windows Azure o cualquier otro servicio que soporte el protocolo OData.  La biblioteca cliente genera peticiones HTTP a un servicio de OData y transforma los datos del feed de respuesta en objetos en el cliente.

Nota

El cliente OData para Windows Phone 7 está disponible como una biblioteca independiente que se puede descargar e instalar desde la página de descarga en CodePlex Open Data Protocol—client libraries.

Este tutorial contiene las siguientes secciones:

  • Descripción de los servicios OData para Windows Phone
  • Enlazando datos a controles
  • Paginación y Navegación
  • Manteniendo el Estado de la Aplicación
  • Generando clases de datos cliente

Los ejemplos del uso de Silverlight se ejecutan en el navegador para simular su comportamiento para Windows Phone. El comportamiento real puede ser ligeramente diferente en el emulador de Windows Phone o en un dispositivo Windows Phone.

Descripción de los servicios OData para Windows Phone

Las aplicaciones de dispositivos móviles dependen en gran medida de fuentes de datos remotas, y la plataforma Windows Azure proporciona una excelente fuente de datos para tu aplicación de Windows Phone.  OData es uno de los mecanismos primarios de acceso de datos de la plataforma Windows Azure.  Varios componentes de la plataforma de Windows Azure producen feeds OData, incluyendo Almacenamiento en Tablas de Windows Azure, Microsoft SQL Azure y el Marketplace de Windows Azure.  Una lista completa de feeds públicos OData está disponible en el Sitio Web de Open Data Protocol.

También puedes utilizar los WCF Data Services para exponer tus propios datos como feeds OData que puedan ser consumidos en las aplicaciones que ejecutan en una variedad de plataformas, incluyendo Windows Phone.  Para obtener más información, consulta WCF Data Services. 

A modo de ejemplo, Netflix expone su catálogo de películas como un feed de OData basado en la nube.  Consumimos este feed OData público en el siguiente ejemplo que es la base para este tutorial:

image

Haz clic en  el mosaico OData on Phone para iniciar el ejemplo del servicio OData.  En el teléfono, cuando tocas el botón Next Page (Página siguiente) la siguiente página de datos se carga desde el servicio de datos.  Cuando tocas un elemento de la lista, se despliega la información detallada acerca del título seleccionado.  En este ejemplo, el botón Atrás trabaja como lo hace en el teléfono navegando de vuelta a la página anterior.  Puedes descargar el proyecto completo para esta aplicación Windows Phone  en el proyecto de Silverlight para Windows Phone en el sitio web de Galería de Código en MSDN.

Enlazando datos a controles

En la biblioteca de cliente OData para Windows Phone 7, la clase DataServiceCollection representa una colección dinámica de enlace de datos que proporciona notificaciones cuando los elementos se añaden o se quita de la colección.  Una consulta basada en URI determina cuáles objetos de datos la colección tendrá.  Este URI es especificado como un parámetro en el método LoadAsync de la clase DataServiceCollection.  Cuando se ejecuta, este método devuelve un feed OData que materializado en objetos de datos en la colección de enlace.  Los objetos materializados son administrados por el DataServiceContext que está asociado con la colección de enlace.  El método LoadAsync de la clase DataServiceCollection asegura que los resultados sean transportados al hilo correcto, para que no necesites utilizar el objeto Dispatcher.  Para obtener más información sobre cómo trabajar con la biblioteca cliente OData para Windows Phone 7, consulta Información general de Open Data Protocol (OData) para Windows Phone.

Debido a que las aplicaciones de Windows Phone requieren de navegación entre múltiples páginas, debes utilizar un patrón de diseño Model-View-ViewModel (MVVM) para tus aplicaciones de datos.  En este patrón, el modelo es generado por las herramientas basadas en los metadatos devueltos por el servicio de datos, la vista se compone de todos los controles enlazados en la página, y el ViewModel es un componente compartido que hace el trabajo de acceder al servicio de datos y exponer los datos que están enlazados a la vista.  Con este enfoque, puedes exponer el DataServiceContext como una propiedad de la clase del ViewModel, junto con las propiedades que regresan instancias de DataServiceCollection o cualquier otro valor dinámico que estén vinculadas a los controles en la vista.  El ViewModel también debe exponer las propiedades que necesitan ser almacenadas cuando se desactiva la aplicación, lo cual discutiremos en una sección posterior.  El siguiente diagrama de objetos representa la clase MainViewModel para este ejemplo:

El siguiente ejemplo muestra las definiciones de las propiedades en la clase MainViewModel que exponen las instancias DataServiceContext y DataServiceCollection:

C#

 

// Define la raíz URI del servicio de datos.

private static readonly Uri rootUri = new Uri("http://odata.netflix.com/v1/Catalog/");

// Define los tipos DataServiceContext.

private NetflixCatalog _context;

// Define la colección de enlaces para los títulos.

private DataServiceCollection<Title> _titles;

// Obtiene y establece la colección de Títulos de objetos de la alimentación.

public DataServiceCollection<Title> Titles

{

get { return _titles; }

private set

{

// Establecer la colección Clientes.

_titles = value;

// Registro de un controlador para la devolución de llamada LoadCompleted.

_titles.LoadCompleted += OnTitlesLoaded;

// Provoca el evento PropertyChanged.

NotifyPropertyChanged("Titles");

}

}

La clase MainViewModel se expone como una propiedad estática de la clase raíz de la aplicación (App), tal como se muestra a continuación:

C#

 

static MainViewModel _viewModel = null;

// Un ViewModel  estático utilizado para las vistas

public static MainViewModel ViewModel

{

get

{

// Retrasa la creación del modelo de vista hasta que lo necesite.

if (_viewModel == null)

{

   _viewModel = new MainViewModel();

}

return _viewModel;

}

}

El ViewModel se establece como el DataContext de la página, lo que nos permite enlazar el control ListBox en MainPage.xaml al DataServiceCollection retornado por la propiedad Titles de la clase MainViewModel.  Se puede ver en el siguiente ejemplo de XAML que el ListBox y algunos otros elementos están vinculados a las propiedades de MainViewModel:

XAML

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Titles}"

SelectionChanged="OnSelectionChanged" Height="Auto">

<ListBox.ItemTemplate>

<DataTemplate>

<StackPanel Margin="0,0,0,17" Width="432" Orientation="Horizontal">

<Image Source="{Binding Path=StreamUri}"

Height="75" Width="50" />

<StackPanel Orientation="Vertical">

<StackPanel.Resources>

<converter:TruncateSynopsis x:Key="synopsis" />

</StackPanel.Resources>

<TextBlock Text="{Binding Path=ShortName}" TextWrapping="Wrap"

Style="{StaticResource PhoneTextLargeStyle}"/>

<TextBlock Text="{Binding Path=ShortSynopsis,

Converter={StaticResource synopsis}}"

TextWrapping="Wrap" Margin="12,-6,12,0"

Style="{StaticResource PhoneTextSubtleStyle}"/>

</StackPanel>

</StackPanel>

</DataTemplate>

</ListBox.ItemTemplate>

</ListBox>

</Grid>

<StackPanel Grid.Row="2" Orientation="Horizontal">

<TextBlock Name="PagingInfo" Text="{Binding PagesLoadedText}" Margin="12,20,10,28"

      Width="270" Style="{StaticResource PhoneTextNormalStyle}" />

<Button Name="MoreButton" Content="Next Page"

      HorizontalAlignment="Right" Width="185" Height="80" Click="MoreButton_Click" />

</StackPanel>

Cuando se carga la página MainPage.xaml, la propiedad IsDataLoaded en el ViewModel es comprobada.  Cuando los datos no están cargados, el método LoadData en el ViewModel es llamado para establecer los objetos de enlace y los títulos de petición del servicio de datos.

C#

// Se utiliza para determinar si se cargan los datos.

public bool IsDataLoaded { get; private set; }

// Carga los datos cuando la aplicación se inicializa.

public void LoadData()

{

// Instancia el contexto y la colección de enlaces.

Context = new NetflixCatalog(_rootUri);

Titles = new DataServiceCollection<Title>(Context);

// Carga los datos

Titles.LoadAsync(GetQueryUri());

}

Cuando el método LoadAsync es llamado, la biblioteca cliente envía de forma asincrónica una consulta al servicio OData que devuelve un feed Atom de entradas de tipo Title.  Cuando la respuesta es recibida, el evento LoadCompleted se dispara y las entradas en el feed son materializadas por la biblioteca cliente en objetos de la colección de títulos, que está enlazada al control ListBox.

OData define un mecanismo para acceder a datos binarios independientes de la entidad a la que pertenecen.  De esta manera, un servicio de datos puede exponer a grandes datos binarios como un recurso de medios, que pertenece a vínculo de entrada de medios.  En el catálogo de Netflix, la entidad Title es un vínculo de medios con un recurso relacionado a medios, que es el arte de la caja del título.  El método GetReadStreamUri en el ViewModel, llama al método GetReadStreamUri en el DataServiceContext para devolver la URI del recurso de los medios.  El siguiente ejemplo muestra la definición de una propiedad de extensión StreamUri que devuelve el URI del recurso de los medios para el enlace:

C#

// Extiende la clase Title para enlazar el URI de recursos

public partial class Title

{

public Uri StreamUri

{

get

{

// Obtiene el URI para los recursos de medios

return App.ViewModel.GetReadStreamUri(this);

}

}

}

 

En este ejemplo, la propiedad StreamUri llama al método GetReadStreamUri para devolver el URI del recurso de medios, que es la imagen del arte de la caja.  Cuando enlazamos la propiedad StreamUri al atributo Source de un control de Image, el URI retornado es usado descargar y mostrar el arte de la caja para cada título.

 

Recomendación

Para un vínculo de medios, te recomendamos que obtengas el URI del recurso del medio llamando GetReadStreamUri en vez de alguna otra propiedad de la entidad.  A pesar que la URI del recurso de medios funciona bien en este caso para crear una imagen en el cliente, otros métodos de DataServiceContext pueden ser usados para acceder y modificar los recursos de medios como un stream binario.

 

Paginación y navegación

En este ejemplo, el URI de la consulta es construido utilizando los parámetros de paginación  definidos en la aplicación.  En OData hay dos tipos diferentes de paginación.  Un cliente puede utilizar las opciones de consulta $top y $skip para limitar el número de entradas en la respuesta a una página lógica en lugar del feed completo.  El servicio de datos también puede limitar el número de entradas devueltas en una respuesta dada, cuando la respuesta contiene un token de continuación que se utiliza para obtener la siguiente página de datos del servicio de datos.  

 

Debido a que el cliente no es capaz de determinar si un servicio de datos implementa la paginación del servidor o incluso el tamaño de la página, debes considerar la implementación de la paginación del lado del cliente en las aplicaciones de Windows Phone para evitar que una consulta devuelva un feed muy grande, el cual puede tomar tiempo y consumir recursos valiosos en el teléfono.  También debes estar preparado para manejar la paginación del lado del servidor en tu aplicación.

 

A continuación se muestra el método GetQueryUri de este ejemplo el cual devuelve el URI para la consulta, que se crea basándose en las variables pasadas en los parámetros $top y $skip.

 

C#

// Método privado que devuelve la consulta específica para páginas.

private Uri GetQueryUri()

{

// Construye y devuelve el URI de la consulta para la siguiente página de resultados.

var queryUri = new Uri(string.Format("/Titles?$top={0}&$skip={1}",

_pageSize, _currentPage * _pageSize), UriKind.Relative);

if (_currentPage == 0)

{

// Si esta es la primera página, a continuación, también incluye entonces un conteo de todos los títulos.

queryUri = new Uri(queryUri.ToString() +

"&$inlinecount=allpages", UriKind.Relative);

}

return queryUri;

}

 

En este ejemplo, el tamaño de página es fijo, y el número de entradas a saltar se calcula basándose en el número de página actual.  Cuando se carga la primera página, también incluimos la opción de consulta $inlinecount=allpages para solicitar un conteo total de todas las entidades del título, que se utiliza para calcular el número total de páginas que son mostradas en TitlesPage.xaml.             

Cuando se recibe la respuesta, el cliente dispara el evento LoadCompleted.  Cuando hay páginas adicionales para cargar debido a que es paginación del lado del servidor, la propiedad Continuation de DataServiceCollection devuelve una token de continuación.  El siguiente método maneja el evento LoadCompleted para cargar todas las páginas del lado del servidor:

C#

private void OnTitlesLoaded(object sender, LoadCompletedEventArgs e)

{

if (Titles.Continuation != null)

{

Titles.LoadNextPartialSetAsync();

}

/ / Establecer el número total de páginas, si se solicita uno.

if (e.QueryOperationResponse.Query

.RequestUri.Query.Contains("$inlinecount=allpages"))

{

_totalCount = (int)e.QueryOperationResponse.TotalCount;

}

IsDataLoaded = true;

/ / actualiza PagesLoadedText

NotifyPropertyChanged("PagesLoadedText");

}

 

Nota que también leemos y almacenamos el valor de TotalCount cuando se solicita en la consulta.  Este valor se utiliza para calcular y mostrar el número total de páginas.  Debido a que al actualizar TotalCount se cambia el valor de la propiedad PagesLoadedText, también reportamos este cambio al enlace.

 

La siguiente página lógica de los datos se solicita cuando el usuario presiona el botón Next Page en MainPage.xaml.  Si bien puedes crear un nuevo DataServiceCollection basado en el URI que devuelve la página siguiente, en vez de eso el siguiente ejemplo utiliza navegación para cargar una página específica de datos:

C#

private void MoreButton_Click(object sender, RoutedEventArgs e)

{

if (App.ViewModel.IsDataLoaded)

{

// Navega a la siguiente pagina de datos

this.NavigationService.Navigate(

new Uri("/TitlesPage.xaml?page="

+ (App.ViewModel.CurrentPage + 1), UriKind.Relative));

}

}

Esencialmente esta navegación vuelve a cargar la página MainPage.xaml con el número de páginas de la página siguiente.  Cuando se navega hacia la página MainPage.xaml, una página específica de datos es cargada desde el servicio de datos, de la siguiente manera:

C#

protected override void OnNavigatedTo(NavigationEventArgs e)

{

if (!App.ViewModel.IsDataLoaded)

{

App.ViewModel.LoadData();

}

else

{

if (this.NavigationContext.QueryString.Count == 1)

{

// Obtener el valor de la página solicitada.

int page = int.Parse(this.NavigationContext.QueryString["page"]);

// Compruebe si la página está cargada.

if (page != App.ViewModel.CurrentPage)

{

// Cargar datos de la página específica.

App.ViewModel.LoadData(page);

}

}

else

{

// Si no hay un parámetro de consulta, estamos en la primera página.

App.ViewModel.LoadData(0);

}

}

}

 

Al utilizar la navegación para cargar las páginas posteriores, cuando el usuario presiona el botón Atrás el comportamiento por defecto es volver a cargar la página anterior.  Sin la navegación, tendrías que reemplazar el método OnBackKeyPress para cargar la página anterior de datos.

 

Cuando el usuario presiona un título en el control ListBox, el siguiente método navega hacia TitlesDetailPage.xaml para el título seleccionado:

 

C#

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)

{

var selector = (Selector)sender;

if (selector.SelectedIndex == -1)

{

return;

}

this.NavigationService.Navigate(

new Uri("/TitleDetailsPage.xaml?selectedIndex="

+ selector.SelectedIndex, UriKind.Relative));

selector.SelectedIndex = -1;

}

 

En la página TitleDetailsPage.xaml, el valor del índice seleccionado se utiliza para enlazar el título correcto de la colección de títulos, de la siguiente manera:

C#

protected override void OnNavigatedTo(NavigationEventArgs e)

{

string indexAsString = this.NavigationContext.QueryString["selectedIndex"];

int index = int.Parse(indexAsString);

this.DataContext = this.currentTitle

= (Title)App.ViewModel.Titles[index];

}

 

 Manteniendo el Estado de la Aplicación

El sistema operativo Windows Phone permite que sólo una aplicación se ejecute a la vez.  Cuando el usuario se desplaza fuera de la aplicación, el sistema operativo finaliza la aplicación. Para habilitar que la aplicación se reactive correctamente, los datos cargados, la información de estado de los objetos, y algunas propiedades del ViewModel deben ser almacenados en el diccionario de estados.  El cliente de OData para Windows Phone 7 introduce una clase DataServiceState que se utiliza para almacenar el DataServiceContext y una colección de objetos DataServiceCollection.  El siguiente método SaveState en el ViewModel devuelve este objeto DataServiceState en una colección de elementos de pares clave/valor, junto con otros datos del ViewModel que deben ser persistidos:

 

C#

// Devuelve una colección de pares clave-valor para almacenar en el estado de la aplicación.

public List<KeyValuePair<string, object>> SaveState()

{

if (App.ViewModel.IsDataLoaded)

{

List<KeyValuePair<string, object>> stateList

= new List<KeyValuePair<string, object>>();

// Crear un nuevo diccionario para almacenar colecciones de enlaces.

var collections = new Dictionary<string, object>();

// Agrega la colección actual de títulos

collections["Titles"] = App.ViewModel.Titles;

// Guarda el contexto actual y las colecciones de enlace en el estado del ViewModel

stateList.Add(new KeyValuePair<string, object>(

"DataServiceState", DataServiceState.Save(_context, collections)));

stateList.Add(new KeyValuePair<string, object>("CurrentPage", CurrentPage));

stateList.Add(new KeyValuePair<string, object>("TotalCount", TotalCount));

return stateList;

}

else

{

      return null;

}

}

 

El siguiente método maneja el evento Deactivated y llama al método SaveState para almacenar los datos del ViewModel cuando la aplicación se desactiva:

 

C#

// Código que se ejecuta cuando la aplicación se desactiva (enviado a un segundo plano).

// Este código no se ejecutará cuando la aplicación se cierre.

private void Application_Deactivated(object sender, DeactivatedEventArgs e)

{

if (App.ViewModel.IsDataLoaded)

{

// Almacenar cada par clave-valor en el diccionario de estado.

foreach (KeyValuePair<string, object> item in App.ViewModel.SaveState())

{

   PhoneApplicationService.Current.State[item.Key] = item.Value;

}

}

}

 

Cuando la aplicación es reactivada, ViewModel se recuperado del diccionario de estados.  El siguiente método maneja el evento Activated para restaurar la aplicación a su estado anterior:

C#

// Código que se ejecuta cuando la aplicación es activada

/ / Este código no se ejecutará cuando la aplicación se lanza por primera vez.

private void Application_Activated(object sender, ActivatedEventArgs e)

{

App.ViewModel.RestoreState(PhoneApplicationService.Current.State);

}

 

En el siguiente método RestoreState, el objeto DataServiceState y otros datos almacenados se adquieren del diccionario global de estado  y son usados para reiniciar el ViewModel:

C#

/ / Restaura el estado del ViewModel desde el diccionario de estados.

public void RestoreState(IDictionary<string, object> dictionary)

{

//crea un diccionario para guardar cualquier colección de enlaces almacenada.

Dictionary<string, object> collections;

// Obtiene el objeto DataServiceState almacenado del diccionario.

var state = dictionary["DataServiceState"] as DataServiceState;

if (state != null)

{

// Restaurar el contexto y las colecciones de enlace.

NetflixCatalog context

= state.Restore(out collections) as NetflixCatalog;

// Obtener la colección de enlaces de título de objetos.

DataServiceCollection<Title> titles

= collections["Titles"] as DataServiceCollection<Title>;

// Iniciar la aplicación con los datos almacenados.

App.ViewModel.LoadData(context, titles);

// Restaurar otros datos del ViewModel

_currentPage = (int)dictionary["CurrentPage"];

  _totalCount = (int)dictionary["TotalCount"];

}

}

Generando clases de datos cliente

Puedes utilizar la herramienta DataSvcUtil.exe para generar las clases de datos en tu aplicación que representan el modelo de datos de un servicio OData.  Esta herramienta, que se incluye con las bibliotecas cliente OData en CodePlex, se conecta al servicio de datos y genera las clases de datos y el contenedor de datos, que hereda de la clase DataServiceContext.  El siguiente comando genera un modelo de datos cliente basado en el servicio de OData de Netflix:

Comando

 

DataSvcUtil.exe /out:"Netflix.cs" /uri:"http://odata.netflix.com/v1/Catalog" /language:csharp

/DataServiceCollection /version: 2.0

 

 

Al utilizar el parámetro /DataServiceCollection  en el comando, las clases DataServiceCollection se generan para cada colección en el modelo.

 

La herramienta DataSvcUtil.exe genera el contenedor de datos con el mismo nombre que el espacio de nombres del esquema.  Para simplificar las referencias, el espacio de nombres de las clases de datos generadas fue modificado manualmente.