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.
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)
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
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
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>
That's all for now.
Next time we will look at building an XML Document using XDocument and XElement.