Introducción a Silverlight – Parte 3: Acceso a datos

Traducción aproximada del artículo Getting started with Silverlight: Part 3 – Accessing Data publicado el 6 de Octubre del 2009 en inglés por Tim Heuer.


Esta es la tercera parte de una serie introductoria sobre desarrollo con Silverlight. Las soluciones resultantes de la aplicación en este ejemplo están disponibles para descarga en C# y VisualBasic.

Ahora que ya tenemos el diseño inicial y algunos controles con los cuales trabajar, empecemos con el acceso a los datos. Como vamos a usar la función de búsqueda de Twitter, vamos a aprovechar la interfase de programación vía web que ofrecen. Nuestra aplicación no necesita una base de datos ni nada por el estilo, pero quiero de todos modos señalar las varias formas en que se puede acceder a información con Silverlight antes de seguir adelante con la nuestra.


Opciones para acceso a datos

Uno de los errores más comunes que cometen los principiantes en Silverlight es buscar algún tipo de biblioteca de clases de ADO.NET para manejar datos. Más vale que paren porque no la van a hallar. Recuerden que Silverlight es una tecnología que ejecuta un programa local servido a través de  la red. Nadie querría darle acceso directo a la base de datos a un módulo de expansión del navegador, forzando la base de datos a estar abierta a la Internet. Todos sabemos que tal práctica es muy mala idea.

Así que el siguiente paso lógico es ofrecer los datos a través de un estrato de servicios. Esta es la manera como Silverlight maneja información. Los principales mecanismos son:

  • Servicios de Web: SOAP, servicios ASP.NET (ASMX), servicios WCF, POX, terminales REST
  • Sockets: comunicaciones de red basadas en sockets o zócalos
  • Archivos: contenido estático mediante peticiones de web


Sockets

El uso de sockets es probablemente el método más avanzado de acceso a datos. Es prerrequisito tener un socket que escuche al otro lado, y los puertos utilizables están muchas veces sujetos a restricciones impuestas por los servidores de seguridad. Si tal situación es aceptable, este tipo de comunicaciones es eficiente y flexible. No obstante, no creo que sea la forma normal de acceso para aplicaciones de web. Sockets es más útil en aplicaciones muy específicas. Información útil para sockets:

Para usar sockets hay que primero entender bien el modelo de uso y distribución de la aplicación y estar seguro de que es el mejor método en tales circunstancias.


Archivos

Silverlight se puede comunicar con datos locales o en la web. En un contexto local, es importante entender que la aplicación no tiene acceso directo al sistema de archivos. La única forma de leer o escribir un archivo es si el usuario inicia la acción vía OpenFilDialog o SaveFileDialog.

Otra forma es mediante archivos de texto o XML disponibles en la web que pueden ser leídos o escritos usando el protocolo HTTP tradicional. La siguiente es información adicional sobre estos métodos:

Estas técnicas pueden ser útiles para guardar preferencias del usuario o como un simple sistema de acceso a datos.


Servicios de web

Este es el mecanismo fundamental para acceso a información en Silverlight: a través de un estrato de servicios.  Por ejemplo, se pueden usar servicios ASP.NET (ASMX) o basados en WCF. En ese caso, Visual Studio ofrece el comando Agregar referencia de servicio que genera código de conexión usando los tipos declarados en el servicio.

También se puede hacer conexión a terminales REST o XML básico (Plain Old XML o POX) usando las acciones estándar del HTTP. Aprender bien a usar estos diferentes tipos de servicios es probablemente la mejor manera en que un desarrollador puede entrenarse para sabe cuál es más apropiado en cada circunstancia. Estos recursos pueden ser de ayuda:

El tercer inciso, .NET RIA Services (ahora conocidos como Servicios WCF RIA), es una nueva infraestructura diseñada para simplificar el acceso a datos. El enlace al vídeo da una introducción paso a paso sobre el tema. Los Servicios RIA son la mejor opción cuando la base de datos es propia y los servicios de web residen en el mismo servidor que provee la aplicación Silverlight.


Acceso asincrónico

Todo acceso a información en Silverlight es asincrónico. Ésta es otra de la áreas que puede al principio ser escabrosa para desarrolladores de web. Por ejemplo, en un programa ejecutado en el servidor sería normal encontrar algo como:


MiServicioDeWeb svc = new MiServicioDeWeb();
string foo = svc.ObtenerUnDato();
MiCampoDeTexto.Text = foo;


En Silverlight no es posible hacer ese tipo de llamada sincrónica. Para los que no han programado en un entrono asincrónico, eso puede resultar muy confuso, pero vale la pena aprenderlo ya que los hará mejores desarrolladores. El seudocódigo anterior adaptado a Silverlight sería:


MiServicioDeWeb svc = new MiServicioDeWeb();
svc.Completed += new CompletedHandler(AlCompletar);
svc.ObtenerUnDato();

void AlCompletar(object sender, EventArgs args)
{
    MiCampoDeTexto.Text = args.Resultado;
}


Noten que uno usa el resultado de la llamada en el controlador del evento Completed. Este es el patrón de uso que verán vez tras vez en el acceso básico a servicios.


Acceso a través de dominios

Por ser un cliente de web, Silverlight opera dentro del entorno protegido del navegador y está sujeto a ciertas directivas de acceso. Una de estas restricciones es conocida como acceso a través de dominios. Quiere decir que la aplicación no puede acceder servicios en un dominio diferente al de origen a menos que el servicio lo permita explícitamente. Este sistema de consentimiento previo se implementa con los llamados archivos de directivas a través de dominios. Al igual que otros clientes de este tipo, Silverlight se somete a tales directivas. Esta es un área que todo programador de Silverlight tendrá que enfrentar en algún momento. Por eso es mejor aprenderla cuanto antes. Aquí les listo algunas guías:

De hecho, vamos a tener que someternos a directivas de ese tipo debido a que nuestra aplicación para Twitter solicitará información de un domino diferente al que la hospeda. Afortunadamente, el servicio de búsqueda de Twitter permite tal acceso en su archivo de directivas de acceso a través de dominios (http://search.twitter.com/crossdomain.xml). Otras áreas en Twitter no permiten tan libre acceso, por lo que actualmente no se podrían usar desde Silverlight. En un caso como ése, habría que crear un servicio que represente al cliente Silverlight y le haga disponibles tales funciones con un archivo de directivas propio. ¿Enredado? Es más simple de lo que parece.


MALENTENDIDO COMÚN: “Es necesario tener los archivos de directivas de Flash y de Silverlight para permitir acceso”. ESO NO ES CIERTO. Frecuentemente veo a gente diciendo “Tengo ambos archivos, crossdomain.xaml y clientaccesspolicy.xml, y aún no trabaja.” Si están creando un servicio para consumo de Silverlight a través de dominios, sólo necesitan el archivo clientaccesspolicy.xml – ese es el primer archivo buscado y el más flexible y seguro para Silverlight.


Ya con una perspectiva general, ¡empecemos a acceder nuestros datos!


Usando la interfase de programación de Twitter

La función de búsqueda de Twitter es una simple interfase tipo REST a la que solamente mandaremos acciones GET en nuestra aplicación. El formato a usar es el de la especificación Atom, facilitando nuestra tarea por ser un formato estandarizado y para el que Silverlight incluye bibliotecas compatibles.

La operación se inicia cuando hay algo en el campo de búsqueda y el usuario pulsa el botón BUSCAR. Conectemos un controlador al evento clic del botón como hicimos en la primera parte de la serie con la aplicación Hello World. En Búsqueda.xaml voy a agregar el evento Click al botón Buscar. Luego agrego un controlador para el evento llamado BuscarTweets:


<Button x:Name="Buscar"
        Height="28"
        HorizontalAlignment="Left"
        Margin="0,0,20,0"
        Width="100"
        RenderTransformOrigin="0.5,0.536"
        Content="BUSCAR"
        Click="BuscarTweets"/>


Al hacer clic derecho en el nombre de la función y escoger Navegar al controlador de eventos, Visual Studio generará la función en el código subyacente. En este método vamos a usar la interfase de Twitter para buscar mensajes que contengan los términos especificados. Como la interfase es una simple acción GET en REST, vamos a usar la funcionalidad WebClient de Silverlight. Cuando no es necesario alterar los encabezados de HTTP, ésta es la interfase de redes más sencilla para la escritura y lectura de datos usando los comandos GET y POST. Pero antes de eso, declaremos algunas variables para llevar cuenta de parámetros en las operaciones de búsqueda:


private const string URI_DE_BUSQUEDA = "http://search.twitter.com/search.atom?q={0}&since_id={1}";
private string _últimoId = "0";
private bool _recibióÚltimo = false;


Ahora podemos componer nuestra función BuscarTweets. ¿Recuerdan que mencioné que la actividad de red en Silverlight es asíncrona? Aquí es donde empezamos a verlo. Vamos a usar la función OpenReadAsync de WebClient. Como la función es asíncrona, necesitamos implementar un controlador para su evento Completed que reciba y procese la respuesta. He aquí lo que tenemos hasta el momento:



private void BuscarTweets(object sender, RoutedEventArgs e)
{
    WebClient proxy = new WebClient();
    proxy.OpenReadCompleted +=
       new OpenReadCompletedEventHandler(proxy_OpenReadCompleted);
    proxy.OpenReadAsync(new Uri(string.Format(URI_DE_BUSQUEDA,
                        HttpUtility.UrlEncode(Termino.Text),
                        _últimoId)));
}

void proxy_OpenReadCompleted(object sender,
                             OpenReadCompletedEventArgs e)
{
    throw new NotImplementedException();
}


Noten que primero creamos un objeto WebClient. Luego agregamos el controlador del evento Completed, y finalmente llamamos la función OpenReadAsync con un URI de formato correcto. La respuesta recibida en el controlador de Completed, e.Result, es en forma de un flujo de bytes. Como vamos a tener que procesar un poco la  respuesta, primero crearemos una clase que represente la estructura que tiene el resultado de una búsqueda. En mi caso, le di le nombre ResultadoDeBusqueda.cs y la puse en una carpeta llamada Model:


using System;
using System.Windows.Media;

namespace MonitorTwitter.Model
{
    public class ResultadoDeBusqueda
    {
        public string Autor { get; set; }
        public string Tweet { get; set; }
        public DateTime FechaPublicado { get; set; }
        public string ID { get; set; }
        public ImageSource Avatar { get; set; }
    }
}


Con este modelo podemos ahora darle forma al resultado y comenzar a ligar o atar datos a los elementos de la interfase al usuario.


Otras opciones de comunicación: HttpWebRequest y ClientHttp

Hay otras dos formas de obtener acceso a la interfase de Twitter: HttpWebRequest y ClientHttp. HttpWebRequest es prácticamente lo que ya estamos usando, pues WebClient no es más que un envoltorio para simplificar la interfase. Si se necesita ajustar los encabezados HTTP para tener un control más fino sobre las peticiones de web, entonces es necesario usar HttpWebRequest. Ambos métodos, WebClient y HttpWebRequest, usan la pila de protocolos de red del navegador, lo que puede a veces imponer ciertas limitaciones. Por ejemplo, no es posible recibir todos los códigos de estado de las respuestas y tampoco se pueden usar algunos comandos avanzados como PUT y DELETE. Para poder usar tales comandos y recibir todos los códigos de estado (además de  200 y 404) Silverlight provee otra opción, ClientHttp, que usa una pila de protocolos propia.

Para más información:

A manera de ejemplo, la manera de usar ClientHttp en nuestro código sería:


private void BuscarTweets(object sender, RoutedEventArgs e)
{
    bool httpBinding = WebRequest
            .RegisterPrefix("http://search.twitter.com",
                            WebRequestCreator.ClientHttp);

    WebClient proxy = new WebClient();
    proxy.OpenReadCompleted +=
            new OpenReadCompletedEventHandler(OnReadCompleted);
    proxy.OpenReadAsync(new
            Uri(string.Format(URI_DE_BUSQUEDA,
                              HttpUtility.UrlEncode(SearchTerm.Text))));
}


En nuestra aplicación no vamos a usar este método, simplemente quería demostrar cómo se puede utilizar. La llamada a RegisterPrefix sirve para indicar vamos a usar esa dirección de web con la pila de protocolos de red de ClientHttp en vez de la del navegador. En el ejemplo registramos una dirección de red específica, sin embargo es posible también activar la pila alternativa para todas las peticiones de HTTP.

Estas son opciones adicionales a considerar en sus propias aplicaciones.


Empecemos con algunas ligas de datos simples a objetos inteligentes

Puesto que nuestra  aplicación va a ‘monitorear’ términos de búsqueda en Twitter, lo que realmente queremos es ligar una colección de objetos a la UI y luego solamente manipular la colección (en nuestro caso, añadir elementos). Para hacer esto, vamos a usar dos objetos útiles en Silverlight: ObsevableCollection<T> y PagedCollectionView. ObservableCollection es una colección que automáticamente provee notificaciones cuando sus elementos son modificados (ya sea añadidos, removidos, o alterados). PagedCollectionView se usa para ayudarnos a organizar nuestros objetos.

Agreguémoslos como miembros en nuestro proyecto:


private ObservableCollection<ResultadoDeBusqueda>
    resultadosDeBusqueda = new
        ObservableCollection<ResultadoDeBusqueda>();

private PagedCollectionView vistaPaginada;


Con estos miembros ya declarados, vamos a inicializar el PagedCollectionView en el constructor del control para tenerlo listo cuanto antes. También queremos ligar nuestra UI a los elementos en el XAML. La práctica recomendada es no modificar la UI dentro del constructor del UserControl (en este caso Busqueda.xaml). Debido a eso, agregamos allí un controlador para el evento Loaded donde configuramos las ligaduras de datos. La combinación de constructor y controlador para Loaded se ve de esta manera:


public Busqueda()
{
    InitializeComponent();

    vistaPaginada = new
        PagedCollectionView(resultadosDeBusqueda);
    vistaPaginada.SortDescriptions.Add(
        new System.ComponentModel.SortDescription("PublishDate",
        System.ComponentModel.ListSortDirection.Ascending));

    Loaded += new RoutedEventHandler(Busqueda_Loaded);
}

void Busqueda_Loaded(object sender, RoutedEventArgs e)
{
    Resultados.ItemsSource = vistaPaginada;
}


En el controlador de Loaded asignamos el PagedCollectionView, ordenado por fecha (ver SortDescription en la línea 8 arriba) a la propiedad ItemsSource del DataGrid (Resultados). Ahora tenemos la interfase al usuario ligada a este PagedCollectionView, así que la idea es llenarlo con información. Recuerden que en realidad es sólo una vista de la información en ObservableCollection<ResultadoDeBusqueda> por lo que en éste último objeto es donde necesitamos añadir datos para poder verlos.


Llenando nuestra ObservableCollection

Regresemos al método OnReadCompleted que será llamado cuando la petición a la función de búsqueda en la red es satisfecha. Es en esta función donde poblaremos la colección observable. Vamos a usar este código:


void proxy_OpenReadCompleted(object sender,
                             OpenReadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        _recibióÚltimo = false;
        XmlReader lector = XmlReader.Create(e.Result);

        SyndicationFeed suministro =
            SyndicationFeed.Load(lector);
        foreach (var item in suministro.Items)
        {
            resultadosDeBusqueda.Add(
                new ResultadoDeBusqueda()
                {
                    Autor = item.Authors[0].Name,
                    ID = GetTweetId(item.Id),
                    Tweet = item.Title.Text,
                    FechaPublicado = item.PublishDate
                                .DateTime.ToLocalTime(),
                    Avatar =
                        new BitmapImage(item.Links[1].Uri)
                });

            _recibióÚltimo = true;
        }

        lector.Close();
    }
    else
    {
        ChildWindow cuadroError = new ErrorWindow(e.Error);
        cuadroError.Show();
    }
}

private string GetTweetId(string id)
{
    string[] partes = id.Split(':');
    if (!_recibióÚltimo)
    {
        _últimoId = partes[2];
    }
    return partes[2];
}


Hay varias cosas ocurriendo aquí. Primero, e.Result corresponde al flujo de bytes recibido como respuesta en una búsqueda exitosa. Si ocurre un error, usamos la plantilla provista, ErrorWindow, cuando la aplicación fue creada al principio. Para evitar tener resultados duplicados, llevamos cuenta del ID de último tweet recibido. La variable _recibióÚltimo sirve para saber si necesitamos actualizar el ID a un nuevo máximo. Al recibir la respuesta, la cargamos en un XmlReader para que asista al SyndicationFeed en interpretarla. Para poder usar esta última clase hay que agregar una referencia a System.ServiceModel.Syndication al proyecto. La biblioteca tiene facilidades para interpretar formatos de suministros RSS y Atom.

NOTA (en guerra avisada…): La biblioteca System.ServiceMode.Syndication trae consigo otras dependencias. A pesar de ser conveniente, no es un archivo pequeño. Hay que tener cuidado al usarla y entender bien cuándo y por qué es necesaria. La estamos usando aquí para demostrar sus cualidades y  beneficios. Otro método posible es simplemente usar LINQ to XML e interpretar el XDocument resultante luego de cargarlo (especialmente si sólo se van a leer suministros sindicados). De nuevo, con fines de demostración, he querido señalar un uso productivo de la clase de tipo restringido SyndicationFeed.

Más información sobre datos sindicados se puede encontrar aquí:

Una vez que tenemos la información en SyndicatedFeed, no más hay que añadir, uno por uno, los nuevos ResultadoDeBusqueda a nuestra ObservableCollection<ResultadoDeBusqueda>. Notarán que estamos convirtiendo el URI de la imagen a un ImageSource con el fin de hacerlo más fácil de ligar a un control luego. Además, analizamos el ID del tweet para extraer un valor que asignaremos al primer resultado (que es el  más nuevo) y que indica el ID más reciente para futuras consultas (_útimoID).


Manteniendo informados a los usuarios

Como último paso, queremos asegurar a nuestros usuarios de que estamos haciendo algo (durante la operación de búsqueda). Afortunadamente, la tarea es fácil con el ActivityControl. Por el momento, el control es parte de las plantillas provistas por los Servicios WCF RIA, pero también se puede descargar del blog de David Poll. Van a tener que generar el control y luego agregar una referencia al proyecto (si bajan uno de los proyectos asociados con esta guía, el control ya viene incluido en la carpeta Libraries).

INFORMACIÓN ACTUALIZADA: Aunque la información de la guía sigue válida, el control ActivityControl es ahora llamado BusyIndicator y es parte del Juego de Herramientas del Silverlight. Utilizando el mismo procedimiento se puede usar el nuevo control oficial e integrarlo en la aplicación. Ya no hay necesidad de compilarlo.

Cuando tengan la referencia lista, pueden añadir una similar usando xmlns en Busqueda.xaml tal y como hicimos con DataGrid en la parte 2. Luego insertamos el control como contenedor base y ponemos el Grid adentro. El archivo Busqueda.xaml se ve ahora así:


NOTA: El ejemplo abajo usa el control del juego de herramientas.


<navigation:Page xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 mc:Ignorable="d"
                 xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
                 xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
                 x:Class="MonitorTwitter.Views.Busqueda"
                 d:DesignWidth="640"
                 d:DesignHeight="480"
                 Title="Busqueda Page">
    <controlsToolkit:BusyIndicator x:Name="IndicadorDeActividad">
        <Grid x:Name="LayoutRoot">

            <Grid.RowDefinitions>
                <RowDefinition Height="32" />
                <RowDefinition />
            </Grid.RowDefinitions>

            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="Termino"
                         TextWrapping="Wrap"
                         Margin="0,0,20,0"
                         Width="350"
                         FontSize="18.667" />
                <Button x:Name="Buscar"
                        Height="28"
                        HorizontalAlignment="Left"
                        Margin="0,0,20,0"
                        Width="100"
                        RenderTransformOrigin="0.5,0.536"
                        Content="BUSCAR"
                        Click="BuscarTweets"/>
            </StackPanel>

            <data:DataGrid x:Name="Resultados"
                           Grid.Row="1"
                           Margin="0,10,0,0" />

        </Grid>
    </controlsToolkit:BusyIndicator>
</navigation:Page>


En el código que inicia la búsqueda (en BuscarTweets) simplemente añadimos:


IndicadorDeActividad.IsBusy = true;


y en el controlador del evento OpenReadCompleted:


IndicadorDeActividad.IsBusy = false;


Y con eso daremos indicaciones visuales de progreso al usuario. Podemos ahora ejecutar (F5) la aplicación y teclear un término de búsqueda. El indicador de actividad será visible:


Indicador de actividad activo durante la búsqueda.


Y luego la vista con los resultados en la cuadrícula:


Al terminar la búsqueda el indicador de actividad desaparece.


Usando temporizadores para el monitoreo

Puesto que lo llamamos un servicio de monitoreo, la idea es que la búsqueda sea refrescada automáticamente. Silverlight provee diferentes maneras de activar tareas automáticas. Nosotros vamos a usar un DispatchTimer en la aplicación. Esto no es más que un temporizador que dispara un evento Tick a intervalos definidos. Agreguemos otra variable a la clase:


private DispatcherTimer _temporizador;


y luego en el constructor la inicializamos y le asignamos un controlador al evento Tick:


double intervalo = 30.0;
#if DEBUG
intervalo = 10.0;
#endif

_temporizador = new DispatcherTimer();
_temporizador.Interval = TimeSpan.FromSeconds(intervalo);
_temporizador.Tick += new EventHandler(_temporizador_Tick);


Como queremos que BuscarTweets sea llamado en el controlador del temporizador, tenemos que reorganizar algunas partes del código.


Aquí me desvío un poco de la ruta tomada por Tim. El resultado es el mismo, pero la reorganización es algo diferente.


Lo que vamos a hacer es crear un nuevo controlador para el evento Click del botón Buscar, llamado NuevaBúsqueda. Dentro del controlador simplemente llamamos a BuscarTweets. La diferencia es que ahora podemos invocar el método desde otros lugares en el código. Específicamente, desde _temporizador_Tick, y desde el controlador del evento Loaded para que arranque el temporizador y también la búsqueda inicial (noten que en modo de depuración —DEBUG— el intervalo es de 10 segundos, mientras que normalmente es 30 segundos). Nuestro código reorganizado en Busqueda.xaml.cs se ve así:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;
using System.Windows.Browser;
using MonitorTwitter.Model;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Xml;
using System.ServiceModel.Syndication;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace MonitorTwitter.Views
{
    public partial class Busqueda : Page
    {
        public Busqueda()
        {
            InitializeComponent();

            _vistaPaginada = new PagedCollectionView(_resultadosDeBusqueda);
            _vistaPaginada.SortDescriptions.Add(
                new System.ComponentModel.SortDescription("PublishDate",
                System.ComponentModel.ListSortDirection.Ascending));

            Loaded += new RoutedEventHandler(Busqueda_Loaded);

            double intervalo = 30.0;
#if DEBUG
            intervalo = 10.0;
#endif
            _temporizador = new DispatcherTimer();
            _temporizador.Interval = TimeSpan.FromSeconds(intervalo);
            _temporizador.Tick += new EventHandler(_temporizador_Tick);
        }

        private void _temporizador_Tick(object sender, EventArgs e)
        {
            BuscarTweets();
        }

        private void Busqueda_Loaded(object sender, RoutedEventArgs e)
        {
            Resultados.ItemsSource = _vistaPaginada;
            _temporizador.Start();
            BuscarTweets();
        }

        // Se ejecuta cuando el usuario navega a esta página.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        /// <summary>
        /// El usuario pulsó el botón BUSCAR, así que empezamos
        /// una nueva operación de búsqueda.
        /// </summary>
        private void NuevaBúsqueda(object sender, RoutedEventArgs e)
        {
            BuscarTweets();
        }

        /// <summary>
        /// Método principal. Busca en Twitter los términos
        /// especificados en Terminos.Text.
        /// </summary>
        private void BuscarTweets()
        {
            if (string.IsNullOrEmpty(Termino.Text)) return;

            _temporizador.Stop();
            IndicadorDeActividad.IsBusy = true;

            WebClient proxy = new WebClient();
            proxy.OpenReadCompleted +=
                new OpenReadCompletedEventHandler(proxy_OpenReadCompleted);
            proxy.OpenReadAsync(new Uri(string.Format(URI_DE_BUSQUEDA,
                                HttpUtility.UrlEncode(Termino.Text),
                                _últimoId)));
        }

        /// <summary>
        /// Proceso de la respuesta a una operación de búsqueda.
        /// </summary>
        private void proxy_OpenReadCompleted(object sender,
                                    OpenReadCompletedEventArgs e)
        {
            IndicadorDeActividad.IsBusy = false;

            if (e.Error == null)
            {
                _recibióÚltimo = false;
                XmlReader lector = XmlReader.Create(e.Result);

                // SyndicationFeed interpreta el formato Atom.
                // Luego agregamos lso resultados a la colección.
                SyndicationFeed suministro = SyndicationFeed.Load(lector);
                foreach (var item in suministro.Items)
                {
                    _resultadosDeBusqueda.Add(
                        new ResultadoDeBusqueda()
                        {
                            Autor = item.Authors[0].Name,
                            ID = GetTweetId(item.Id),
                            Tweet = item.Title.Text,
                            FechaPublicado = item.PublishDate
                                        .DateTime.ToLocalTime(),
                            Avatar =
                                new BitmapImage(item.Links[1].Uri)
                        });

                    // Obtuvimos resultados. => tenemos máximo Id.
                    _recibióÚltimo = true;
                }

                lector.Close();
            }
            else
            {
                ChildWindow cuadroError = new ErrorWindow(e.Error);
                cuadroError.Show();
            }

            _temporizador.Start();
        }

        /// <summary>
        /// Obtiene el númro identificador del tweet.
        /// </summary>
        private string GetTweetId(string id)
        {
            string[] partes = id.Split(':');
            if (!_recibióÚltimo)
            {
                // Oops: ¿SRP?
                _últimoId = partes[2];
            }
            return partes[2];
        }

        private ObservableCollection<ResultadoDeBusqueda>
            _resultadosDeBusqueda = new
                ObservableCollection<ResultadoDeBusqueda>();
        private PagedCollectionView _vistaPaginada;
        private DispatcherTimer _temporizador;

        private const string URI_DE_BUSQUEDA =
            "http://search.twitter.com/search.atom?q={0}&since_id={1}";
        private string _últimoId = "0";
        private bool _recibióÚltimo = false;
    }
}


La secuencia final de operación es: cargar la página Busqueda, iniciar el temporizador y la primera búsqueda. Cuando el intervalo expira, la búsqueda es ejecutada de nuevo, pero recuerda el ID del último Tweet recibido para evitar resultados duplicados. Los resultados de esa nueva búsqueda son agregados al ObservableCollection y, como el DataGrid está ligado a la colección, son desplegados en la interfase al usuario ordenados por fecha.

También añadimos una prueba para verificar que al ejecutar una búsqueda haya términos en el campo y así evitar buscar un valor en blanco.


Resumen

Para ahora ya hemos avanzado mucho en esta tercera parte. Nos conectamos a un servicio provisto por un agente externo y canalizamos los resultados, refrescados periódicamente, al DataGrid. Podríamos parar aquí, pero no lo haremos. El DataGrid no es la mejor manera de presentar los resultados. En la cuarta parte, vamos a usar plantillas para dar formato a la información e introduciremos la sintaxis de ligado de datos de XAML.

Etiquetas asignadas:
 

7 Respuestas a “Introducción a Silverlight – Parte 3: Acceso a datos”

  1. Ninoska dice:

    Hola como seria este acceso a datos aplicando MVVM. Sin utilizar RIA Gracias

    • David Mora dice:

      Ninoska,

      Los métodos de acceso a datos en el artículo son solamente conductos para obtenerlos. Por cierto, RIA no es más que otro medio similar, aunque más sofisticado en ciertos aspectos. Estos “conductos” no tienen relación con el patrón MVVM en sí. Sin embargo, es cierto que la acción propiamente de traer los datos y de manipularlos de acuerdo con las reglas de la aplicación caen en el ámbito del modelo. El modelo se encarga de representar esa información de manera que sea compatible con el “dominio” de la aplicación y de mantener el estado de los datos (también llamado el contexto).

      Un problema al usar el MVVM es que la implementación está sujeta a interpretación. La forma en que yo lo haría sería usar comandos desde la vista para que el modelo de vista le indique a modelo que obtenga los datos de Twitter. El modelo se encarga de esa operación y luego procesa la información recibida y crea o repuebla el ObservableCollection que el modelo de vista luego pasa a la vista.

  2. rodolfo dice:

    excelente articulo, fue de bastante utilidad

  3. luisterio dice:

    buenisimo el manual… me sirvio muchisimo.. y funciono con cada instruccion… se agradece la claridad con que tomas cada uno de los temas.

  4. Jhon dice:

    Excelente los manuales… siempre e sido desarrollador de aplicaciones Desktop; pero con este forma de usted explicar estoy metido de lleno en SilverLight

Responder



Licencia de uso

El contenido de las traducciones está sujeto a los términos de protección de derechos de uso de los autores originales quienes han autorizado su publicación en este blog. Asegúrese de entender los terminos de la licencia de cada autor antes de usar tal contenido.

Mis propios artículos son publicados bajo los términos de la Licencia Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos de Creative Commons:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos License.

License

The contents of all translated articles is subject to the copyright and licensing terms of the original authors and has been published here with their express permission. Verify the original author's licensing terms before using the contents of these articles.

My own articles are licensed under the Creative Commons Attribution-Share Alike 3.0 United States License:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.