Create a RSS Reader in .NET 3.5 using XLINQ

Visual Studio 2008 and .NET 3.5 are finally out and with it we have some new "toys" to look at.  Today we are going to look at XLINQ and the new ListView control while we build a new RSS Reader.  Before we get started I want to take a brief at LINQ.

LINQ (Language Integrated Query) defines a set of operators used to query, filter, and project data.  While the data must be encapsulated as an object, its source can be an array, enumerable class, XML, relational database, or any third party data source.  Some of the operators we have will be familiar to SQL programmers such as Select, Where, Join, Concat, OrderBy, Disctinct, and Union.  We will look at the syntax in a bit.  For further reading on LINQ be sure to check out the LINQ Project at Microsoft.

To get started we are going to need:
.NET 3.5 Framework
Visual Studio 2008

So lets get started by opening our development environment VS2008 and create a new Web project. 
In this project we are going to add the following:
Rss.vb (Class file)
GetRss.vb (Class file)
rss-reader.ascx (User Control)

Rss.vb

Looking at the code below I want to talk mainly about the LINQ portion.  But before we get going on it lets take a brief look at XDocument. In LINQ to XML (XLINQ) we use XDocument or XElement to load the XML into an object for us to query.  XDocument uses XMLReader as its underlying object to read the XML document and thus requires an XMLReader, TextReader, or URI to be passed into XDocument.  Once we have it loaded we will access it through the Descendants Method and Element Method. 

As you look closer at the query you can see that we are loading the node "channel" into an Anonymous type . 

myFeed In feedSource.Descendants("channel")

By using an Anonymous type we don't have to define a class declaration of the type.  We can instead use this type inline and access the elements of the node(s) we loaded into it and set them to properties of our collection with out having to define the properties.

feedDescription = myFeed.Element("description").Value

We can also create a sub collection with in our query and return it as part of the object to later be processed.

feedItems = myFeed.Descendants("item")

We finally we are returning our query as IEnumerable object to later be bound to a Listview in our class control.

 

Public Class RSS
    Public Function Read(ByVal URLPath As String) As IEnumerable
        Dim dcNameSpace As XNamespace
        dcNameSpace = XNamespace.Get("http://purl.org/dc/elements/1.1/")
        Dim feedSource As XDocument
        feedSource = XDocument.Load(URLPath)

        Dim query = From myFeed In feedSource.Descendants("channel") _
                    Select feedTitle = myFeed.Element("title").Value, _
                            feedDescription = myFeed.Element("description").Value, _
                            feedLink = myFeed.Element("link").Value, _
                            feedAuthor = myFeed.Element(dcNameSpace + "creator").Value, _
                            feedItems = myFeed.Descendants("item")
        Return query
    End Function
End Class


GetRss.vb

This is a simple class.  We are passing in URLPath (URL to our Feed) to be processed.  As you can see we call the read method of our RSS object (our DAL) and return it to our class control.  This class file is our BLL.  It is in this layer we would apply any business rules needed.

Public Class GetRSS
    Private m_urlPath As String
    Public Function GetRSS() As IEnumerable
        Dim myRSS As New RSS


        Return myRSS.Read(m_urlPath)
    End Function

    Public Sub New()

    End Sub

    Public Sub New(ByVal UrlPath As String)
        m_urlPath = UrlPath
    End Sub
End Class

Rss-Reader.ascx

Now to the fun part of putting it all together.  First the code behind. 

Simply we set a property for the class to hold URLpath, and bind the object to the our list view in our page_load.  However we have another method in our codebhind that I'm calling XEval.  The problem here is the sub collection inside our IEnumerable object.  When we get the collection at this point we are finding 2 main property's exposed.  One is value, and the other is XML.  We will see how we use both of these when we look at the markup.  Briefly though Value gives us the value of the property, and XML gives us the XML markup of that same property.  Neither of which are queriable (that I was able to find).  So what we are doing in XEval is passing the XML property to this method, along with the Xpath we want to return the InnerText of that node.

Imports System.Xml
Partial Public Class rss_reader
    Inherits System.Web.UI.UserControl

    Private m_urlPath As String
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim myRSS As New GetRSS(m_urlPath)
        listview1.DataSource = myRSS.GetRSS
        listview1.DataBind()

    End Sub

    Public Function xEval(ByVal x As String, ByVal xPath As String) As String
        Dim xDocument As New XmlDocument
        xDocument.LoadXml(x)

        x = xDocument.SelectSingleNode(xPath).InnerText
        Return x
    End Function

    Public Property URLPath() As String
        Get
            Return Me.m_urlPath
        End Get
        Set(ByVal value As String)
            Me.m_urlPath = value
        End Set
    End Property

End Class

Now the markup.

Here I want to look at the ListView.  ListView's can be bound to an object that Inherits IEnumerable, SQLDatasource, LINQDatasource, XMLDatasource, and ObjectDatasource.  In our ListView we are using the LayoutTemplate which has a placeholder inside of a div and the ItemTemplate.  There are other templates available to us such as AlternateItemTemplate, HeaderTemplate, FooterTemplate, and EmptyItemTemplate.  Each of these templates will be rendered into the placeholder in the layout template.  So as you can see this gives us flexability in defining the look and feel of our control.  Scott Guthrie has a great blog entry giving more information on ListView's.  For mine here I'm a CollapsiblePanelExender control from Ajax to provide a bit of control and styling.

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="rss-reader.ascx.vb" Inherits="Sandbox.rss_reader" %>
<%@ Register assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" tagprefix="cc1" %>

<asp:ListView ID="listview1" runat="server">
            <LayoutTemplate>
                <div style="width: 1000px;">
                    <asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
                </div>
            </LayoutTemplate>            
            <ItemTemplate>
                <div>
                    <asp:Hyperlink ID="hlin2" runat="server" style="font-weight: bold; font-size: 12pt; color: Black;" Text='<%# Eval("feedTitle") %>' NavigateUrl='<%# Eval("feedLink") %>' />
                </div>
                <div>    
                    <asp:Label ID="label1" runat="server" style="font-weight: bold; font-size: 12pt;" Text='<%# Eval("feedDescription") %>' />
                </div>
                <div><hr /></div>           
                <div>
                    <asp:ListView ID="listview2" runat="server"  DataSource='<%# Eval("feedItems") %>' >
                        <LayoutTemplate>
                            <div>
                                <asp:placeHolder ID="itemPlaceHolder" runat="server" />
                            </div>
                        </LayoutTemplate>
                        <ItemTemplate>
                            <div>
                                <div id="descriptControl" runat="server" style="height: 30px;">
                                <div style="float: left;">
                                    <asp:HyperLink ID="hlink1" runat="server" Text='<%# xEval(Eval("XML"), "/item/title") %>' NavigateUrl='<%# xEval(Eval("XML"), "/item/link") %>' />
                                </div>
                                <div style="float:right;">
                                    <asp:ImageButton ImageUrl="~/expand.jpg" ID="imgbtn" runat="server" />
                                </div>
                                </div>                                
                               <asp:Panel ID="panel1" runat="server" BorderStyle="Inset" BorderColor="#333333" BorderWidth="1">
                                    <div>
                                        <asp:Label ID="label2"  style="font-size: 9pt; font-style:italic; color: Gray;" runat="server" Text='<%# xEval(Eval("XML"), "/item/pubDate") %>' />
                                    </div>
                                    <div id="descriptDiv" runat="server" style="padding: 3px;">
                                        <asp:Label ID="label3"  style="font-size: 9pt;" runat="server" Text='<%# xEval(Eval("XML"), "/item/description") %>' />
                                    </div>
                                </asp:Panel>
                                <cc1:CollapsiblePanelExtender ID="CollapsiblePanelExtender1" runat="server" Collapsed="true" 
                                    TargetControlID="panel1" ExpandControlID="imgbtn" CollapseControlID="imgbtn"
                                     CollapsedImage="expand.jpg" ExpandedImage="collapse.jpg" SuppressPostBack="true">
                                </cc1:CollapsiblePanelExtender>
                            </div>         
                        </ItemTemplate>
                    </asp:ListView>
                </div>        
            </ItemTemplate>
        </asp:ListView>
And the final output:
 
image
 

 

Download Sourcecode

 

That's all for now.

Next time we will look at building an XML Document using XDocument and XElement.

Dave

4 Comments

  • As an fyi, VB provides shortcuts for many of the LINQ to XML clauses like descendants. You can rewrite that line of code to be:

    Dim query = From myFeed In feedSource... _
    Select feedTitle = myFeed..Value, _
    feedDescription = myFeed..Value, _
    feedLink = myFeed..Value, _
    feedAuthor = myFeed..Value, _
    feedItems = myFeed.

    Note I'm doing this in your editor here, so I may have missed something, but this should give you the idea.

    Enjoyed the blog entry!

    yag

  • You can also get an rss feed by putting it into a dataset with the dataset.readxml method.

  • I can't get this to work, I get 'Object reference not set to an instance of an object' in rss-reader.acsx on the listview1.DataBind() line. Any one got any idea why?

  • Talk about the hard way... load the rss file into a string, throw it into an xml datasource, and use the grouping feature in the listview (so you only have to use one listview) and xpath expressions to get the data you need.. You can even detect the rss version and decide which xpath epressions to use for the data...

Comments have been disabled for this content.