I have a subscription to msdn magazine and am a regular reader. I especially try to read all the articles on any ASP.NET related topics. Dino Exposito is a regular contributor to this magazine and has written many great articles on ASP.NET. In the April 2008 he wrote an excellent article on the new ASP.NET ListView control, specially, he discusses in detail how to bind a ListView control to hierarchical data.
This is by no means the first article that I have read on the topic of ListView controls. In all the articles or blog postings that I have read, I have yet to see how to use LINQ to create custom objects with complex object graphs. Instead anonymous types are used. In this post I will demonstrate how to create a custom object graph from a LINQ to XML query. Furthermore, I will bind this object to a nested ListView control. I will use the same example as presented by Dino in the August edition of msdn magazine.
We will be creating a hierarchical menu that is rendered using LINQ to XML, ListView control and an XML file. Listing 1 shows the menu.xml file.
Listing 1: menu.xml
<?xml version="1.0" encoding="utf-8" ?>
<menu>
<item>
<title>Menu Title 1</title>
<link url="url1-1" text="text1-1"></link>
<link url="url1-2" text="text1-2"></link>
<link url="url1-3" text="text1-3"></link>
<link url="url1-4" text="text1-4"></link>
</item>
<item>
<title>Menu Title 2</title>
<link url="url2-1" text="text2-1"></link>
<link url="url2-2" text="text2-2"></link>
<link url="url2-3" text="text2-3"></link>
</item>
<item>
<title>Menu Title 3</title>
<link url="url3-1" text="text3-1"></link>
<link url="url3-2" text="text3-2"></link>
</item>
</menu>
The schema of this XML file is straight forward. Each <item> node contains a menu title and then has 1 or more links. Each link has the URL to navigate too and the title that is to be used as textual display for the link.
Ultimately we would like to bind this data to a nested ListView control. The markup for this nested ListView control is shown in Listing 2. The title is render in the outer ListView control, whereas, the links are render in the inner ListView control. The code bind will be responsible for binding the outer ListView. The inner is bound declaratively. Please note that DataSource for the inner ListView control must be named links.
List 2: Nested ListView Control
<asp:ListView ID="ListViewMenu" runat="server" ItemPlaceholderID="PlaceHolder1">
<LayoutTemplate>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</LayoutTemplate>
<ItemTemplate>
<h1><%# Eval("title")%></h1>
<asp:ListView ID="ListViewSubMenu" runat="server"
DataSource='<%# Eval("links") %>' ItemPlaceholderID="PlaceHolder2">
<LayoutTemplate>
<ul><asp:PlaceHolder ID="PlaceHolder2" runat="server"></asp:PlaceHolder></ul>
</LayoutTemplate>
<ItemTemplate>
<li><a href='<%# Eval("url") %>'><%#Eval("text")%></a></li>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
Before we use custom objects, let's review how we can use LINQ to XML and anonymous types as a data source to a ListView control. Listing 3 shows the code behind that accomplishes this.
List 3: Binding ListView Control with an Anonymous Type
Dim doc As XDocument = XDocument.Load(Server.MapPath("App_Data/Menu.xml"))
Dim menu = From mi In doc.<menu>.<item> _
Select _
title = mi.<title>.Value, _
links = From link In mi.<link> _
Select _
url = link.@url, _
[text] = link.@text
ListViewMenu.DataSource = menu
ListViewMenu.DataBind()
In this code snippet we are using the LINQ to XML features of VB.NET to query the XML file. The doc.<menu>.<item> retrieves all the <item> children for the document. The content of the <item> node is loaded into an anonymous type having two properties; title and links. The property links is a collection that is populated by the sub query. In the end, the anonymous type, menu is of type IEnumerable(Of <anonymous type>). The menu object is then used as data source to the outer ListView control.
Anonymous type are great, however, there are cases when you may need to pass the data between methods, tiers or even across process boundaries. In this scenario you should us a custom type. To do this you must first create the classes. In our case two classes are required. Listing 4 shows the two classes.
List 4: Custom Types
<DebuggerStepThrough()> _
Public Class Menu
<DebuggerBrowsable(DebuggerBrowsableState.Never)> _
Private _title As String
Public Property title() As String
Get
Return _title
End Get
Set(ByVal value As String)
_title = value
End Set
End Property
<DebuggerBrowsable(DebuggerBrowsableState.Never)> _
Private _links As IEnumerable(Of Link)
Public Property links() As IEnumerable(Of Link)
Get
Return _links
End Get
Set(ByVal value As IEnumerable(Of Link))
_links = value
End Set
End Property
End Class
<DebuggerStepThrough()> _
Public Class Link
<DebuggerBrowsable(DebuggerBrowsableState.Never)> _
Private _url As String
Public Property url() As String
Get
Return _url
End Get
Set(ByVal value As String)
_url = value
End Set
End Property
<DebuggerBrowsable(DebuggerBrowsableState.Never)> _
Private _text As String
Public Property text() As String
Get
Return _text
End Get
Set(ByVal value As String)
_text = value
End Set
End Property
End Class
The two classes are named, Menu and Link. Menu contains two properties; title and links. links is of type IEnumerable(Of Link) which is important as LINQ queries of this type return the generic type IEnumerable(Of T). I have tried other generic types that derive from IEnumerable(Of T) such as List(Of T) but they generate Unable to cast object of type... exception. Perhaps there is a way to explicitly cast the object but that would require further investigation. Perhaps a future blog post?
Listing 5 shows LINQ to XML code to make use of the custom type.
List 5: Binding ListView Control with a Custom Type
Dim doc As XDocument = XDocument.Load(Server.MapPath("App_Data/Menu.xml"))
Dim menu = From mi In doc.<menu>.<item> _
Select New Menu With { _
.title = mi.<title>.Value, _
.links = From l In mi.<link> _
Select New Link With { _
.url = l.@url, _
.[text] = l.@text _
} _
}
ListViewMenu.DataSource = menu
ListViewMenu.DataBind()
The menu object is now of type IEnumerable(Of Menu), which can be easily passed as a parameter to a method or serialized across the wire.
Guess the movie
You loved my father, I know. But so did I. That makes us brothers, doesn't it? Smile for me now, brother.