Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 4
Este es el cuarto artículo de la serie dedicada a crear una aplicación lectora de RSS, en esta parte crearemos la funcionalidad que consume los datos desde un RSS y con esa información llenamos los datos de nuestro modelo, creado en la parte 3.
Consumiendo el RSS por medio de SyndicationClient
Desde Visual Studio damos clic derecho sobre la carpeta Util y creamos una nueva clase llamada RSSHelper cuyo objetivo es abstraer toda la lógica necesaria para consumir el feed.
Esta clase debemos marcarla como estática y pública
using RSSJuanK4Blog.Model;
using System.Threading.Tasks;
namespace RSSJuanK4Blog.Util
{
public static class RSSHelper
{
}
}
Dentro de esta clase definimos un método estático que devuelva una lista de artículos, este método debe trabajar de manera asíncrona por lo que hacemos uso de async, lo cual de paso implica que devuelva un Task.
using RSSJuanK4Blog.Model;
using System.Threading.Tasks;
namespace RSSJuanK4Blog.Util
{
public static class RSSHelper
{
public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl)
{
return null;
}
}
}
Ahora implementaremos el método, creamos un objeto SyndicationClient y por medio de este traemos los datos del feed, por medio del feed traemos los artículos expuestos en el RSS, los cuales se exponen como ítems. Es importante notar que hay que agregar algunos namespace para poder utilizar las clases Uri y SyndicationClient:
using RSSJuanK4Blog.Model;
using System;
using System.Threading.Tasks;
using Windows.Web.Syndication;
namespace RSSJuanK4Blog.Util
{
public static class RSSHelper
{
public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl)
{
var syncClient = new SyndicationClient();
var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
foreach (var art in feed.Items)
{
}
return null;
}
}
}
Dentro del método creamos una lista de artículos y la llenamos dentro del foreach creando un nuevo articulo por cada ítem hallado. Este es el código para que sea fácil de entender, noten que estamos llamando algunos métodos que no hemos creado, pero los crearemos luego ;)
public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl)
{
var syncClient = new SyndicationClient();
var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
var lista = new ArticleList();
foreach (var art in feed.Items)
{
//estos métodos aún no los hemos creado, pero lo haremos más adelante
string htmlContent = CreateContent(art.NodeValue);
string summary = CreateSummary(art.Summary,htmlContent);
Uri imgUri = Find1stImageFromHtml(htmlContent);
//Crear el articulo
var newArt = new Article()
{
Title = art.Title.Text,
Content = htmlContent,
Summary = summary,
ImgUri = imgUri
};
//Adicionarlo a la lista
lista.Add(newArt);
}
return lista;
}
Esta es la misma funcionalidad del código anterior pero un poco más corta:
public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl)
{
var syncClient = new SyndicationClient();
var lista = new ArticleList();
var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
foreach (var art in feed.Items)
{
var content = CreateContent(art.NodeValue);
lista.Add(new Article()
{
Title = art.Title.Text,
Content = content,
Summary = CreateSummary(art.Summary, content),
ImgUri = Find1stImageFromHtml(content)
});
}
return lista;
}
Ahora creamos los esqueletos de cada una de las funciones que hemos creado, y seguidamente las implementaremos.
public static async Task< ArticleList >private static string CreateContent(string content)
{
throw new NotImplementedException();
}
private static string CreateSummary(ISyndicationText syndicationText, string htmlContent)
{
throw new NotImplementedException();
}
private static Uri Find1stImageFromHtml(string htmlContent)
{
throw new NotImplementedException();
}
CreateContent
En este método debemos validar que el contenido reportado por el RSS sea válido, esto implica cosas como validar que no sea nulo (puede pasar, créanme), y dado que vamos a mostrar la información en formato html es conveniente extraer del contenido del articulo solo el contenido html ya que usualmente no es lo único que contiene.
Adicionalmente dejaremos abierta la puerta para adicionarle información al inicio y final de ese html lo cual puede ser útil en el futuro.
Usualmente en los feed RSS hay información adicional antes del contenido html, así que este método debe detectar dónde comienza el html y estar incluso preparado cuando en el feed no hay nada de código html.
private static string CreateContent(SyndicationContent content)
{
string htmlString = string.Empty;
string preHtml = string.Empty;
string proHtml = string.Empty;
if (content != null)
{
//Hallar el primer tag html
var htmlBeginIndex = content.IndexOf('<');
//Recortar la cadena desde el primer tag html hallado
if(htmlBeginIndex != -1)
htmlString = content.Substring(htmlBeginIndex);
}
return preHtml + htmlString + proHtml;
}
CreateSummary
En este método extrae un resumen del contenido del RSS, muchos RSS ya reportan esta información automáticamente, lo cual es muy útil, pero muchos otros no lo hacen.
Cuando el resumen no sea reportado vamos a crearlo, y esto lo haremos con ayuda del contenido hallado con el método anterior. Bien sea que reporten o no el resumen una cosa a tener en cuenta es que este resumen puede tener tamaños variados, por lo cual conviene establecer un tamaño máximo para el resumen.
Para efectos de este tutorial el resumen no sera mostrado como html sino como texto plano, para convertir html en texto plano usamos un helper incluido en WinRT llamado HtmlUtilities, para poderlo usar debemos adicionar el namespace Windows.Data.Html.
using Windows.Data.Html;
Así que primero validamos que exista un resumen, sino existe o no es informado procedemos a crearlo con base en el contenido, una vez tenemos el resumen convertimos el html en texto plano y finalmente recortamos el resumen para que no supere un valor máximo establecido. Dicho valor máximo lo hemos establecido por medio de una constante.
const int MAX_ABSTRACT_LEN = 300;
private static string CreateSummary(ISyndicationText syndicationText,
string htmlContent)
{
string summaryText = String.Empty;
if (syndicationText != null && !string.IsNullOrWhiteSpace(syndicationText.Text))
summaryText = HtmlUtilities.ConvertToText(syndicationText.Text);
else
summaryText = HtmlUtilities.ConvertToText(htmlContent);
summaryText = summaryText.Substring(0, Math.Min(summaryText.Length, MAX_ABSTRACT_LEN));
return summaryText;
}
Del código anterior para muchos puede ser un poco misterioso este fragmento
Math.Min(summaryText.Length, MAX_ABSTRACT_LEN));
Este método de la clase Math selecciona el menor de dos números lo cual es conveniente porque si tratamos de recortar una cadena por ejemplo a 300 caracteres pero la cadena no alcanza a tener esa longitud entonces reventaría una excepción, esto lo podemos controlar con un if y un par de validaciones, pero este código nos ahorra varias líneas adicionales.
Find1stImageFromHtml
De los métodos que estamos creando este posiblemente sea el único que se puede considerar complejo, la idea es analizar una cadena html para buscar los tag img y de estos extraer el atributo src, valla tarea... pero con ayuda de expresiones regulares se puede hacer fácilmente, expresiones regulares bien da para todo un curso de 40 horas, pero nos basta con saber que este código lo hace por nosotros. Lo anexo desde mi Github donde comparto este código para toda persona que le interese:
Lo único que debemos hacer es volverlo estático. Para que funcione adecuadamente hay que agregar el namespace:
using System.Text.RegularExpressions;
Ahora que ya esta adicionado hacemos uso de el invocándolo desde nuestro método y convirtiendo el string en un Uri:
private static Uri Find1stImageFromHtml(string htmlContent)
{
return new Uri(ExtractFirstHtmlImage(htmlContent));
}
Hemos concluido el artículo, antes de despedirme y recordarles revisar el próximo artículo les dejo el código completo de lo que se ha implementado:
using RSSJuanK4Blog.Model;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Data.Html;
using Windows.Web.Syndication;
namespace RSSJuanK4Blog.Util
{
public static class RSSHelper
{
public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl)
{
var syncClient = new SyndicationClient();
var lista = new ArticleList();
var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
foreach (var art in feed.Items)
{
var content = CreateContent(art.NodeValue);
lista.Add(new Article()
{
Title = art.Title.Text,
Content = content,
Summary = CreateSummary(art.Summary, content),
ImgUri = Find1stImageFromHtml(content)
});
}
return lista;
}
private static string CreateContent(string content)
{
string htmlString = string.Empty;
string preHtml = string.Empty;
string proHtml = string.Empty;
if (content != null)
{
//Hallar el primer tag html
var htmlBeginIndex = content.IndexOf('<');
//Recortar la cadena desde el primer tag html hallado
if (htmlBeginIndex != -1)
htmlString = content.Substring(htmlBeginIndex);
}
return preHtml + htmlString + proHtml;
}
const int MAX_ABSTRACT_LEN = 300;
private static string CreateSummary(ISyndicationText syndicationText,
string htmlContent)
{
string summaryText = String.Empty;
if (syndicationText != null && !string.IsNullOrWhiteSpace(syndicationText.Text))
summaryText = HtmlUtilities.ConvertToText(syndicationText.Text);
else
summaryText = HtmlUtilities.ConvertToText(htmlContent);
summaryText = summaryText.Substring(0, Math.Min(summaryText.Length, MAX_ABSTRACT_LEN));
return summaryText;
}
private static Uri Find1stImageFromHtml(string htmlContent)
{
return new Uri(ExtractFirstHtmlImage(htmlContent));
}
/// <summary>
/// Extracts the first image Url from a html string
/// </summary>
/// <param name="htmlString">A string containing html code</param>
/// <returns>a string with the Url or first image in the htmlString parameter</returns>
/// <remarks>This method uses regular expressions,so using System.Text.RegularExpressions; must be addeed</remarks>
public string ExtractFirstHtmlImage(string htmlString)
{
string respuesta = TEMPURI;
try
{
var rgx = new Regex(
@"<img\b[^>]*?\b(?(1)src|src)\s*=\s*(?:""(?<URL>(?:\\""|[^""])*)""|'(?<URL>(?:\\'|[^'])*)')",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
var match = rgx.Match(htmlString);
respuesta = match.Groups["URL"].Value;
if (respuesta == "")
respuesta = TEMPURI;
}
catch { respuesta = TEMPURI; }
return respuesta;
}
}
}
Hasta la próxima
Índice General
Para cumplir con el alcance establecido he decidido fraccionar el proyecto en las siguientes partes:
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 1
- Introducción al tutorial
- Let's begin
- Indice General
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 2
- Preparando la solución
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 3
- Modelo de Datos
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 4
- Consumiendo el RSS por medio de SyndicationClient
- CreateContent
- CreateSummary
- Find1stImageFromHtml
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 5
- Inicializando la Aplicación e implementado el View-Model
- Cómo y desde donde llamar a Initialize
- Asociando el DataContext del View
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 6
- Construyendo la UI - Parte 1
- Esquema principal de la App
- Creando elementos básicos
- El titulo
- Aplicar propiedades utilizando estilos
- El icono
- El artículo actual
- La Lista de Artículos
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 7
- Vinculando la View con el ViewModel
- El artículo actual
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 8
- Mejorando la experiencia de usuario - Parte 1
- Hacer que aparezca un articulo seleccionado por defecto
- Disminuir el tamaño de los títulos del ListView
- Disminuir el ancho del ListView
- Evitar que los resúmenes de los artículos en el ListView crezcan de manera descontrolada
- Colocar una imagen dummy en el Listview cuando no existan imágenes en el artículo
- Colocar la imagen adecuada cuando la única imagen del RSS es el aggbug
- Colocar una imagen dummy en el Listview cuando la imagen hallada en el artículo sea demasiado pequeña
- Mientras cargan los datos del feed da la impresión de que la App no esta haciendo nada
- Conclusión
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 9
- Mejorar la apariencia de ListView
- Mejorar la apariencia del ProgressRing
- Mejorar la apariencia del WebView
- Soporte para Snapped View
- Imagen de Fondo
- Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 10
- Mejorando la experiencia de usuario - Parte 2
- Detección de conexión a internet
- Adición de la política de privacidad
- Tareas adicionales
- FIN DEL TUTORIAL
Comments
Anonymous
November 26, 2012
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
November 26, 2012
Preparando la solución Para este proyecto vamos a Visual Studio y creamos una nueva solución de Tipo Windows Store, seleccionamos la plantilla en blanco y la nombramos RSSJuanK4BlogAnonymous
November 26, 2012
The comment has been removedAnonymous
November 26, 2012
The comment has been removedAnonymous
November 26, 2012
The comment has been removedAnonymous
November 26, 2012
The comment has been removedAnonymous
December 10, 2012
Perdona pero el método CreateContent debería recibir un string como parámetro es por eso que me causo la confusión, discúlpame!Anonymous
December 14, 2012
hola tengo problema con esta parte public static async Task< ArticleList > GetArticleListFromFeedAsync(string feedUrl) y no se porque es que sucede.. me dice que es inconsistente porque retorna un tipo y el método recibe otro diferente. me puede ayudar.Anonymous
February 01, 2013
Saludo tengo el siguiente error en la línea 82==> Error 1 'ExtractFirstHtmlImage': cannot declare instance members in a static class c:usersjonathandocumentsvisual studio 2012ProjectsLector de blogLector de blogUtilRSSHelper.cs 82 23 Lector de blogAnonymous
February 01, 2013
Ramón, el método debe ser estático, igual que la clase.Anonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
Preparando la solución Para este proyecto vamos a Visual Studio y creamos una nueva solución de Tipo Windows Store, seleccionamos la plantilla en blanco y la nombramos RSSJuanK4BlogAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
Construyendo la UI - Parte 2 En este artículo trabajaremos sobre algunos aspectos de la Interfaz de UsuarioAnonymous
March 24, 2013
Este es el último post de esta serie, solo nos falta afinar estos dos importantes temas: Detección de conexión a internet Adición de la política de privacidadAnonymous
March 24, 2013
Mejorando la experiencia de usuario - Parte 1 Aplicación terminada? no. Funcionalidad terminada? mmm digamos que si. La aplicación ya hace lo mínimo funcional pero hay que hacerle varios ajustes y mejoras para brindar al usuario una experiencia más agradableAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
Pasadas las primeras 4 partes del tutorial podemos decir que tenemos una buena parte de nuestro 'backendAnonymous
March 24, 2013
Construyendo la UI - Parte 1 Para crear la UI recomiendo siempre contar con un poco de buen gusto estéticoAnonymous
March 24, 2013
Vinculando la View con el ViewModel La UI ya esta creada, bueno al menos la estructura básica y el modeloAnonymous
March 24, 2013
Mejorando la experiencia de usuario - Parte 1 Aplicación terminada? no. Funcionalidad terminada? mmmAnonymous
March 24, 2013
Mejorando la experiencia de usuario - Parte 2 Este es el último post de esta serie, solo nos falta afinarAnonymous
March 24, 2013
Construyendo la UI - Parte 2 En este artículo trabajaremos sobre algunos aspectos de la Interfaz de UsuarioAnonymous
March 24, 2013
Introducción al tutorial Hola, antes de comenzar con lo obvio quiero que se fijen en el titulo de esteAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
The comment has been removedAnonymous
March 24, 2013
The comment has been removed