Dynamically Loading ListView Templates


I received an email question recently from a customer asking how they could dynamically load the LayoutTemplate for a ListView from a UserControl. I banged my head on this one for a while and eventually mailed our internal ASP.NET alias asking if someone could help. Fortunately David Fowler could. Hats off to him – the code below is his solution.

Let’s start with a basic ListView page that we want to convert to load the LayoutTemplate from a UserControl. Something like this will do:

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"></head>
<body>
  <form id="form1" runat="server">
    <asp:ListView ID="ListView1" runat="server"
      DataSourceID="XmlDataSource1">
      <LayoutTemplate>
        <ul>
          <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
        </ul>
      </LayoutTemplate>
      <ItemTemplate>
        <li><%# Eval("id") %></li>
      </ItemTemplate>
    </asp:ListView>
    <asp:XmlDataSource ID="XmlDataSource1" runat="server"
      XPath="/catalog/book"
      DataFile="~/XMLFile.xml">
    </asp:XmlDataSource>
  </form>
</body>
</html>

We have an XmlDataSource (in my case pointing to the MSDN sample XML file) and I’m binding the book ids to list items in an unordered list using a ListView control. Pretty straightforward so far.

Imagine I refactored this a little to use a UserControl for the LayoutTemplate

<%@ Control Language="C#" %>
<ul>
  <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
</ul>

and one for the ItemTemplate

<%@ Control Language="C#" %>
<li>
  <%# Eval("id") %>
</li>

That means I could replace my original aspx file with one that looks like this

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
</head>
<body>
  <form id="form1" runat="server">
    <asp:ListView ID="ListView1" runat="server" 
      ItemPlaceholderID="MyLayout$itemPlaceholder"
      DataSourceID="XmlDataSource1">
    </asp:ListView>
    <asp:XmlDataSource ID="XmlDataSource1" runat="server" 
      XPath="/catalog/book"
      DataFile="~/XMLFile.xml">
    </asp:XmlDataSource>
  </form>
</body>
</html>

ie I’ve defined no LayoutTemplate or ItemTemplate – we’ll load these dynamically. The only other change is I’ve specified an ItemPlaceholderID on the ListView. We’ll see why in a second.

using System;
using System.Web.UI;

public partial class Default2 : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    ListView1.LayoutCreated += new EventHandler(ListView1_LayoutCreated);
    ListView1.LayoutTemplate = LoadTemplate("LayoutTemplate.ascx");
    ListView1.ItemTemplate = LoadTemplate("ItemTemplate.ascx");
  }

  void ListView1_LayoutCreated(object sender, EventArgs e)
  {
    //remove the layout template
    ListView1.Controls.RemoveAt(0);

    //recreate it
    Control newLayoutContainer = new Control();
    ListView1.LayoutTemplate.InstantiateIn(newLayoutContainer);
    var userControl = newLayoutContainer.Controls[0];
    userControl.ID = "MyLayout";
    ListView1.Controls.Add(newLayoutContainer);
  }
}

image If you just go ahead and load the templates from the UserControls, you’ll get the yellow screen of death telling you "An item placeholder must be specified on ListView ‘ListView1’. Specify an item placeholder by setting a control’s ID property to "itemPlaceholder". The item placeholder control must also specify runat="server"." The ListView simply can’t find the ItemPlaceholder.

The key here is that the UserControl is a naming container so the ListView can’t find the ItemPlaceholderID because it’s been modified to avoid naming clashes on the page. In the code above we recreate the LayoutTemplate and set the UserControl‘s ID to "MyLayout" allowing us to specify that the ItemPlaceholderID will be "MyLayout$itemPlaceholder".

You could reduce this by determining up front the "native" ID of the UserControl ("ctl01" in this case) and setting the ItemPlaceholderID property of the ListView to "ctl01$itemPlaceholder". This would allow you to dispense with the LayoutCreated handler altogether. Of course if your UserControl ID then changes, it’s going to break.

Technorati Tags: ,
Comments (12)

  1. 真见 says:

    1.44个令人惊奇的Silverlight视频教程。2.ExpressionEncoder2VB.NETupdate3.HDViewforInternetExplore…

  2. cnblogs.com says:

    1. 44个令人惊奇的Silverlight视频教程。 2. Expression Encoder 2 VB.NET update 3. HD View for Internet Explorer HD

  3. Does the newLayoutContainer’s control collection act as a stack or a queue? It seems to me that the line

      ListView1.Controls.RemoveAt(0);

    would remove your layoutTemplate when you load the ItemTemplate.

    ???

  4. MikeO [MSFT] says:

    Hi Jordan. The LayoutTemplate acts as a container so items become children of the LayoutTemplate.

    Mike

  5. wuz says:

    I guess this applies to template classes as well and so

    namespace PresentationLayer

    {

       public class LayoutTemplate : ITemplate

       {

               LiteralControl table = new LiteralControl("<table><tbody><asp:PlaceHolder runat="server" ID="ctl01$itemPlaceholder" /></tbody></table>");

               container.Controls.Add(table);

           }

       }

    }

    will also say that itemPlaceholder cannot be found cause it changed.

  6. wuz says:

    And i am afraid this solution is impossible when using direct databinding without a datasource

  7. wuz says:

    Hi,

    Thx again this really seems to be the only workaroun unfortuantely it fails when i use user controls or custom controls within the LayoutTemplate:

    http://forums.asp.net/p/1305569/2569260.aspx

    greetings

  8. MikeO [MSFT] says:

    Wuz – I have it working with databinding with no DataSource. My databinding code looks like this:

       XDocument xdoc = XDocument.Load(Server.MapPath("~/XmlFile.xml"));

       var query = from x in xdoc.Descendants("book")

                   select new { id = (string)x.Attribute("id")};

       ListView1.DataSource = query;

       ListView1.DataBind();

  9. wuz says:

    Hello mike,

    Thanks your entry really seems to be the only help for this topic. Unfortunately when I am using your suggestion it works for the first time when i am accessing the page, but not after the postback.

    There seems to be a problem in Begin Loadstate:

    An item placeholder must be specified on ListView ‘lvList’. Specify an item placeholder by setting a control’s ID property to "MyLayout$itemPlaceholder". The item placeholder control must also specify runat="server".

     bei System.Web.UI.WebControls.ListView.GetPreparedContainerInfo(Control outerContainer, Boolean isItem, Int32& placeholderIndex)

     bei System.Web.UI.WebControls.ListView.CreateItemsWithoutGroups(ListViewPagedDataSource dataSource, Boolean dataBinding, InsertItemPosition insertPosition, ArrayList keyArray)

     bei System.Web.UI.WebControls.ListView.CreateChildControls(IEnumerable dataSource, Boolean dataBinding)

     bei System.Web.UI.WebControls.ListView.CreateChildControls()

     bei System.Web.UI.Control.EnsureChildControls()

    I have opened a thread with the complete sourcecode:

    http://forums.asp.net/p/1305569/2558209.aspx

    Thanks again and kind regards

  10. I got quite a few comments on my post on Dynamically Loading ListView Templates so rather than trying

  11. MikeO [MSFT] says:

    Just blogged an updated version. Mike

Skip to main content