Creando un lector de feeds RSS con Silverlight 2

Introducción

En este artículo veremos cómo crear un lector de feeds de RSS 2.0 usando Silverlight 2 como interfaz de usuario, para crear una aplicación que muestre las últimas actualizaciones de una fuente sindicalizada con el formato RSS 2.0.  Este tipo de formato es muy utilizado en los motores de blogging como WordPress, en las plataformas de portales como SharePoint o DotNetNuke entre otro tipo de plataformas; no obstante, no es el único formato para hacer la difusión de contenidos ya que también existe Atom: otro formato similar para la exposición de actualizaciones.

Los formatos RSS como Atom están basados en XML, cada uno con un esquema en particular que los diferencia.  La especificación de RSS (en inglés) la podrán encontrar haciendo clic aquí.

Por otro lado, a partir de la versión 3.5 del .NET Framework se incorporó el namespace System.ServiceModel.Syndication el cual contiene todas las clases necesarias para interpretar y crear este tipo de fuentes de información.

Desarrollo

Comencemos creando una nueva aplicación de Silverlight 2 usando Visual Studio .NET 2008.  A nuestra nueva aplicación le llamaremos FeedSlideShow tal como lo muestra la siguiente figura:

El primer elemento que agregaremos a nuestra aplicación de Silverlight será un User Control llamado FeedBalloon que represente la forma que deseamos que tenga cada uno de los elementos que estamos leyendo del feed.  En el caso de este artículo crearemos una forma tipo “post-it” la cual tenga un título que al hacer clic nos redirija al sitio original del elemento y un cuerpo el cual tenga el texto relacionado con el artículo.  Esto es una tarea sencilla si la realizamos con Expression Blend:

El siguiente fragmento de código muestra el XAML que hemos definido para este ejemplo.  Es importante resaltar que estamos usando elementos “nombrados” para poder tener acceso a ellos cuando creemos cada uno de los elementos en el Canvas de la aplicación.

<UserControl x:Class="FeedSlideShow.FeedBalloon"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot" Width="210">
        <Grid.RowDefinitions>
            <RowDefinition Height="0.351*"/>
            <RowDefinition Height="0.649*"/>
        </Grid.RowDefinitions>
        <Rectangle Stroke="#FF263125" RadiusX="10" RadiusY="10" Grid.RowSpan="2">
            <Rectangle.Fill>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF07701D"/>
                    <GradientStop Color="#FF121211" Offset="1"/>
                </LinearGradientBrush>
            </Rectangle.Fill>
        </Rectangle>
        <HyperlinkButton Margin="8,8,8,8" Foreground="#FF02AAFF" FontSize="12" x:Name="title"/>
        <TextBlock Margin="8,8,8,8" TextWrapping="Wrap" Grid.Row="1" Foreground="#FFD5C7C7" x:Name="body" FontFamily="./Fonts/MyriadPro-Regular.otf#Myriad Pro" />
    </Grid>
</UserControl>

El segundo elemento que implementaremos a nuestro proyecto de Silverlight será una clase llamada Link, la cual representará cada uno de los elementos que estamos leyendo del feed.  Esta clase tendrá las propiedades Title, Body y Url todas de tipo string.  El siguiente fragmento de código muestra esta clase y en unos momentos más veremos cómo la usaremos en esta aplicación:

namespace FeedSlideShow
{
 
    public class Link
    {
        public string Title { get; set; }
 
        public string Body { get; set; }
 
        public string Url { get; set; }
 
    }
}

Ahora, la página de nuestra aplicación:  Page.xaml.  La aplicación tendrá un lienzo de fondo negro con esquinas redondeadas.  Este lienzo es donde se dibujarán cada uno de los elementos del feed.  Este diseño lo podemos lograr facilmente si implementamos el Canvas dentro de un elemento Border con las propiedades deseadas:

<UserControl x:Class="FeedSlideShow.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Border BorderBrush="Black" Background="Black" BorderThickness="3" CornerRadius="10" Padding="10">
            <Canvas x:Name="theCanvas" Background="Black" Width="Auto" Height="Auto">
            </Canvas>
        </Border>
    </Grid>
</UserControl>

Esta aplicación la haremos parametrizable, para que podamos definir fuera de la aplicación el Url de los feeds que deseamos obtener y no tener esa dirección codificada directamente en el proyecto.  Pasar parámetros en una aplicación Silverlight es una tarea muy sencilla, pero depende de si estamos usando <object> o el control <asp:Silverlight> para instanciar nuestro control de Silverlight.  A continuación se muestra el nombre del parámetro correcto para cada una de estos mecanismos:

<object>
InitParams

<asp:Silverlight>
InitParameters

Independientemente de cuál sea el método que usaremos para instanciar el control de Silverlight, los parámetros los podemos obtener en el evento StartUp de la aplicación.  El nombre por defecto de la aplicación en un proyecto de Silverlight es App, no obstante este nombre es totalmente arbitrario y lo podemos renombrar incluso borrar y agregar uno nuevo.  El siguiente fragmento de código muestra la obtención de los parámetros que recibe la aplicación, y cómo los mandamos a la nueva instancia de la página que usaremos (Page) usando su constructor:

private void Application_Startup(object sender, StartupEventArgs e)
{
    string url = e.InitParams["url"];
    this.RootVisual = new Page(url);
}

Es importante aquí mencionar que las páginas en una aplicación Silverlight no son más que clases que heredan la clase UserControl, y podemos modificar su comportamiento –en este caso agregando un argumento al constructor principal en Page.xaml.cs-

string url;
 
public Page(string url)
{
    InitializeComponent();
    this.url = url;
    Loaded +=new RoutedEventHandler(Page_Loaded);
}

Como se puede apreciar en el código, estamos usando un campo privado llamado url el cual guarda el Url que se manda como parámetro desde la instanciación del control Silverlight.

Ahora bien, la funcionalidad principal de nuestra aplicación la implementaremos en el event handler del evento Loaded, es decir, Page_Loaded.  Básicamente, tenemos que hacer las siguientes acciones para leer el feed de RSS en cuestión:

  1. Usar el método OpenReadAsync de la clase WebClient para hacer una petición asíncrona del Url RSS que deseamos leer en forma de un Stream
  2. Una vez obtenido el Stream creamos un objeto de tipo XmlReader para leer ese contenido (finalmente el contenido RSS es XML como lo comentamos con anterioridad)
  3. Además de crear el objeto Stream crearemos un objeto de tipo XmlReaderSettings para determinar la configuración del XmlReader.  Este paso aunque no es necesario es recomendado para indicar exactamente cómo queremos que funcione.
  4. Creamos un objeto de tipo Rss20FeedFormatter y determinamos si podemos leer el objeto XmlReader con él.
  5. Si sí lo podemos leer, obtenemos un objeto de tipo SyndicationFeed de la propiedad Feed.  Este objeto incorpora la propiedad Items la cual representa todos y cada uno de los elementos de ese feed.
  6. Leemos esos elementos y creamos una lista genérica de tipo Link (la clase que implementamos más arriba en este artículo).
  7. Iniciamos un timer que nos ayudará a mostrar elemento por elemento de una manera atractiva dentro del Canvas.

A continuación se muestra el código:

WebClient client = new WebClient();
            client.OpenReadCompleted += (s, a) =>
            {
                if (a.Error == null) //No hay errores
                {
                    //Obtiene el resultado
                    Stream result = a.Result;
                    //Crea los settings para leer el Xml
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.IgnoreWhitespace = true;
                    settings.CheckCharacters = true;
                    settings.CloseInput = true;
                    settings.IgnoreComments = true;
                    settings.IgnoreProcessingInstructions = true;
                    
                    //Creamos el objeto XmlReader
                    XmlReader reader = XmlReader.Create(result, settings);
 
                    Rss20FeedFormatter rss20 = new Rss20FeedFormatter();
 
                    if (rss20.CanRead(reader)) //Si puede leer...
                    {
                        rss20.ReadFrom(reader);
                        SyndicationFeed feed = rss20.Feed;
                        links = new List<Link>(feed.Items.Count());
                        
                        foreach (var item in feed.Items)
                        {
                            links.Add(new Link() { Title = item.Title.Text, Body = HttpUtility.HtmlDecode(item.Summary.Text), Url = item.Links[0].Uri.ToString() });
                        }
 
                        if (links.Count > 0)
                        {
                            timer.Start();
                        }
 
                    }
                }
            };
            client.OpenReadAsync(new Uri(url));

Y a continuación el código del Tick del timer.  Cabe notar que estamos haciendo uso de expresiones Lambda en vez de escribir los cuerpos de los manejadores de eventos (event handlers):

Random r = new Random(0);
  DispatcherTimer timer = new DispatcherTimer();
  timer.Interval = TimeSpan.FromSeconds(6);
  timer.Tick += (s, a) =>
  {
      if (index < links.Count - 1)
      {
          index++;
      }
      else
      {
          index = 0;
      }
 
      Link nextLink = links[index];
      if (nextLink != null)
      {
 
          FeedBalloon feedBalloon = new FeedBalloon();
          feedBalloon.title.Content = nextLink.Title;
          feedBalloon.title.NavigateUri = new Uri(nextLink.Url);
          feedBalloon.body.Text = nextLink.Body;
 
          double nextTop = r.Next(0, (int)(theCanvas.ActualHeight - 200));
          double nextLeft = r.Next(0, (int)(theCanvas.ActualWidth - 200));
 
          feedBalloon.SetValue(Canvas.TopProperty, nextTop);
          feedBalloon.SetValue(Canvas.LeftProperty, nextLeft);
          theCanvas.Children.Add(feedBalloon);
 
 
          //Creamos la animación
          Storyboard sb = new Storyboard();
          DoubleAnimation anim = new DoubleAnimation();
          anim.From = 0;
          anim.To = 1;
          anim.AutoReverse = true;
          anim.Duration = new Duration(TimeSpan.FromSeconds(5));
          Storyboard.SetTarget(anim, feedBalloon);
          Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity"));
          sb.Children.Add(anim);
          sb.Completed += (x,y) =>
          {
              theCanvas.Children.Remove(feedBalloon);
          };
          sb.Begin();
 
      }
 
 
  };

En cada Tick del timer:

  1. Actualizamos un contador que nos servirá para ir barriendo uno por uno de los elementos de la lista
  2. Creamos un objeto de tipo FeedBalloon, el cual como recordaremos, representa el elemento visual que vamos a mostrar en el Canvas.  A este objeto le asignamos en sus elementos los valores que deseamos mostrar.  En el caso de este ejemplo estamos usando propiedades normales del CLR, pero podríamos implementar propiedades dependientes para hacer atado de datos en vez de hacerlo de manera directa.  No obstante, por efectos prácticos para este artículo lo haremos directamente.
  3. Creamos una animación programáticamente.  Esta animación hará un efecto de fadein-fadeout cuando presentemos los elementos del feed.  Es notable la potencia de Silverlight al permitirnos definir vía código este tipo de elementos.
  4. La animación la agregamos a un StoryBoard y lo iniciamos.  Cuando finaliza el StoryBoard, removemos el elemento del Canvas y la historia vuelve a comenzar…

Todo completo

La aplicación finalizada la pueden checar en el sitio de ejemplos de La Liga Silverlight y el código fuente lo pueden descargar de la parte de Contenido en el mismo sitio.

No olviden que la aplicación quedó parametrizada así que si usan, por ejemplo, <object> para instanciar su control de Silverlight, deberán incluir el parámetro con la dirección del Url que desean leer:

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
            <param name="source" value="ClientBin/FeedSlideShow.xap"/>
            <param name="onerror" value="onSilverlightError" />
            <param name="background" value="white" />
            <param name="minRuntimeVersion" value="2.0.31005.0" />
            <param name="InitParams" value="url=http://blogs.ligasilverlight.com/?feed=rss2" />
            <param name="autoUpgrade" value="true" />
            <a href="https://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
                 <img src="https://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
            </a>
        </object>

Resumen

Silverlight nos permite crear aplicaciones con alto impacto visual y que hagan uso de servicios o datos externos, tal es el caso de un feed RSS de un blog o de un portal.  La aplicación que aquí construímos es un ejemplo de esta característica y puede ser usado como base para crear aplicaciones que hagan uso de fuentes sindicadas.