-[Danny Chen]- Blog of an ASP.NET QA tester

Tips and info about Site Navigation, ImageMap, Menu and other cool ASP.NET v2.0 features.
I have a new blog: http://blogs.msdn.com/dannychen

The ASP.NET weblogs are still using .Text which, frankly, is a bit annoying to work with and just plain 'old'.  Pete LePage showed me the MSDN blogs which use the (much newer) Community Server 2.0 codebase so I'm going to switch to it.  They look much cooler anyway.  Hopefully my blog will still be syndicated on ASP.NET so the readers can keep up to date with my happenings.

In other news, you'll notice on the new blog that I'm now a "Visual Web Developer" tester as well.  Actually, the truth is that after ASP.NET 2.0 shipped, I found it to be a prime opportunity to shift jobs and join the test team that is responsible for the GUI.  While both teams are quite integrated, they are certainly distinct and have their own test methodologies.  This is going to be a great experience and I really like my new team. 

However, I can't seem to just let go of controls so I expect you'll continue to see posts from me about custom controls and navigation tricks as well as a strong forum presence. 

Here is the new address: http://blogs.msdn.com/dannychen

IE7 Beta and VWD Style Builder.

There's a bug in the IE7 beta which seems to break the style builder.  Of course we'll work on getting it fixed but in the meantime, if you happen to have IE7 installed, there's a workaround you can apply.

Create a new DWORD reg-key named "devenv.exe" with the value "0" at the following location:

"HKLM\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_CSS_DATA_RESPECTS_XSS_ZONE_SETTING_KB912120"

Read more about it here:  http://blogs.msdn.com/petel

 

Top results for the loading control challenge.

Thank you everyone for sharing your code with me.  There were quite a few excellent submissions and I learned a few things.  Here are the top entries I received so far for the challenge.  I'll add to this list if any more come in that I feel are worth mention.

crms -at- sima.com is an Elite ASP.NET hacker and submitted this solution:
     http://weblogs.asp.net/dannychen/articles/439435.aspx

Raj Kaimal is an Elite ASP.NET hacker and submitted this solution:
     http://weblogs.asp.net/dannychen/articles/439436.aspx
     Raj also gets extra credit for coming up with a sample page using async tasks to demo his control.

Both of these are very fine solutions, but not quite perfect.  Most critically, both run into an issue that I don't know if it can even be worked around.  Intermittently, ASP.NET throws an error:

Session state has created a session id, but cannot save it because the response was already flushed by the application.

I talked with some dev's about this and they seem to think it's pretty cut-and-dry.  Using Response.Flush pretty much causes this and can't really be worked around.  And this can't really be done without Response.Flush.

Secondly, both are hardcoded to display a fixed message.  It would be really nice if they were Templated instead to give the users flexibility in the display.  This is a trivial amount of work, however...

None-the-less, I learned that this was indeed possible and in-fact, not that hard to do.  Hopefully someone else will be able to benefit from these examples. 

 

We are Hiring.

Do you want to help make ASP.NET and Visual Studio even better in the next version?  We are looking for the brightest and the most talented candidates to fill some challenging positions in our team.  If you think you have the skills, guts, and determination to help improve the lives of web developers and shape web technology for the future, follow this link and submit your resume.

http://forums.asp.net/1197654/ShowPost.aspx

UPDATE:  I've received quite a few submissions from people through comments or direct email.  While I really appreciate the interest you've shown, unfortunately, I am not the hiring manager and therefore these decisions are not mine to make.  So, I will not be able to respond to these posts or publish any of the submissions through feedback.  If you're interested, you NEED to follow the link above.

A custom control challenge - make a page loading control that just works.

It is a fairly well known technique to use Response.Buffer and Response.Flush to make a "please wait" or "Loading..." message appear when you know a page is going to take a while to load.  So I was quite estatic when I read on ScottGu's blog that someone (Daniel Fisher) had done what I've wanted to do for a while: formalize this trick into a nice tidy custom control you can just drop onto a page.  You can see it here:  http://www.lennybacon.com/PleaseWaitBuildingAWaitScreenControlForASPNET.aspx.  It is really a very slick control.  However, there was one problem I couldn't (and still can't) figure out which stopped me from writing this control initially.  When I look more closely at Daniel's code, I noticed that he hadn't solved this problem either.

The problem

How do you write the control so that it doesn't wrap a specific function but instead works against the natural page loading time itself.  Here's a scenario to help describe what I mean.  Lets say you develop a page with a couple GridView's and DetailsViews bound to SqlDataSource's.  Everything works great in your mock up but as soon as you deploy it against your live data, you find that the page takes 2-8 seconds to load.  It would be awesome if you could simple take this "loading ..." control, drop it onto your page and -bang- the functionality is added.  Now, I know there's an easy solution using Daniels code and simple adding the DataBinding logic into his OnProcess method, but that's not the point.  This should be a drag-and-drop solution.

The challenge

Create a custom control that provides similar functionality to Daniels but works against the page processing time.  A "drag and drop" kind of solution.  If you think you can do something that even I can't figure out how to do, please contact me through my blog and I will get back to you over email.  I'll post the best submissions I receive and declare the submitters to be "Elite ASP.NET Hackers"

 

Saving unicode content with encoding

Here's a quick tip that you may or may not know about.  In Visual Studio (and possibly other MS programs) it's not enough to paste some unicode content into your file and then hit save.  You'll also need to save it with the correct encoding.  However, once you save the file with a particular encoding, it will continue to use that encoding until you switch it (this means you won't have to keep doing this for each save)

Here are the steps to do this: 

simple choose file->save as
in the save-as dialog the "Save" button has a little pull down arrow.
Select this arrow and then choose "Save With Encoding"
This will open a dialog with a drop down that lists all the supported encodings.

Here are some screenshot to help out:

 

Making the CompiledTemplateBuilder work for you. Create a Template Library.

This is a bit of a follow on to my last post.  At the end, I made an update about how to make use of custom TreeNodes in a databound scenario.  I did that example with the CSSTreeNode instead of the TemplatedTreeNode because the custom property in the templated case was an ITemplate.  Trying to assign a value to that property is a great deal more difficult than a string property.  And that got me thinking...

Lets start with something that seems pretty simple: 

<asp:SiteMapPath ID="SiteMapPath1" runat="server">
    <NodeTemplate>
        <%# Eval("title") %>
    
</NodeTemplate>
</asp:SiteMapPath>

What if you wanted to add this control to the page dynamically?  How would you write it in code?  Well, adding the SiteMapPath to the page is pretty simple.  But the NodeTemplate isn't so simple.  What actually happens in that case is a mechanism in the framework creates a new object on the fly that implements the ITemplate interface and generates some code to populate the template.  In order to do this manually, the developer would have to create his/her own ITemplate object and databinding code.  Here's a simplified version of what that code might look like:

public class MyTemplate : ITemplate
{
    
public void InstantiateIn(Control container)
    {
        
Label l1 = new Label();
        container.Controls.Add(l1);
        l1.DataBinding +=
new EventHandler(l1_DataBinding);    
    }

    
void l1_DataBinding(object sender, EventArgs e)
    {
        
Label l1 = sender as Label;
        
SiteMapNodeItem container = l1.BindingContainer as SiteMapNodeItem;
        l1.Text = container.SiteMapNode.Title;
    }
}
 
public partial class TestPage : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        
SiteMapPath smp = new SiteMapPath();
        smp.NodeTemplate =
new MyTemplate();
        form1.Controls.Add(smp);
    }
}

Keep in mind, this code is for a single eval statement.  If you wanted to add several controls, some styles, maybe another template or two (perhaps 20 lines of markup), this could quickly become 100+ lines of code for this single control.  This is especially unfortunate because this is work that the parser can do reliably and quickly. 

So the question is, how can I get the parser to parse the template markup for me and yet present me with a reference to the ITemplate object instead of consuming it?
  

  

The Template Library Control

What we need is a control (with no rendering, kind of like a datasource) with a public property that is a collection of templates.  Then similarly to how the page parser will populate a ListItem collection or a MenuItem collection for you, it could populate a Template collection.  It'd be additionally nice to have random access to the list through a Name rather than by index.  Amazingly enough, this actually works: (link to full listing at the bottom of the article):

    
    [
ParseChildren(true,"Templates"), PersistChildren(false)]
    
public class TemplateLibrary : Control
    {
        
public TemplateLibrary()
        {
            _templates =
new TemplateList();
        }
        
        
private TemplateList _templates;
        [
PersistenceMode(PersistenceMode.InnerProperty)]
        
public TemplateList Templates
        {
            
get return _templates; }
        }

    }

 
   public class TemplateList : List<TemplateItem>
    {
        
public TemplateItem this[string key] { ... }
    }

    public class TemplateItem : ITemplate
    {
        
private string _name;
        
private ITemplate _template;

        
public string Name { ... }

        [PersistenceMode(PersistenceMode.InnerDefaultProperty),
        
TemplateContainer(typeof(IDataItemContainer))]        
        
public ITemplate Template { ... }

        #region ITemplate Members
        
public void InstantiateIn(Control container) { ... }
        #endregion
    }
}

Here's how this might be used in markup:

<MS:TemplateLibrary  runat="server" ID="TemplateLibrary1">
    <MS:TemplateItem Name="Template1">
        <Template>Some Content</Template>
    </MS:TemplateItem>            
    
<MS:TemplateItem Name="Template2">
        <Template>Some Other Content</Template>
    </MS:TemplateItem>            
</MS:TemplateLibrary>
 
 
protected void Page_Load(object sender, EventArgs e)
{
    Menu1.StaticItemTemplate = TemplateLibrary1.Templates[
"Template1"];
    Menu1.DynamicItemTemplate = TemplateLibrary1.Templates[
"Template2"];
}
 

As shown here, the end result is that the templates are built by the parser but exposed through a public property on the control.  The control itself has no inherent rendering so it doesn't really affect the page output.  Since the templates are assigned in code, some special logic could be used to choose which template should be applied where.  Here are a couple more examples of things you can do that would, otherwise, have been a great deal more difficult to accomplish. 

Example 1: This first example makes uses of the templated TreeNodes from the previous post.  In this case, I'm choosing, dynamically, based on the data in web.sitemap, which template should be applied.

<%@ Page Language="C#" Debug="true" %>
<%
@ Register Namespace="MSSamples" TagPrefix="MS" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<
script runat="server">
    
    
protected void MyTreeView1_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
    {
        
if (e.Node is TemplatedTreeNode)
        {
            
TemplatedTreeNode tn = e.Node as TemplatedTreeNode;
            
SiteMapNode smn = e.Node.DataItem as SiteMapNode;
                        
            
if( smn != null && smn["preTemplate"] != null )
                tn.PreTextTemplate = TemplateLibrary1.Templates[ smn[
"preTemplate"] ];
            
            
if( smn != null && smn["postTemplate"] != null )
                tn.PostTextTemplate = TemplateLibrary1.Templates[ smn[
"postTemplate"] ];
        }
    }

</script>

<
html xmlns="http://www.w3.org/1999/xhtml">
<
head id="Head1" runat="server">
    <title>Untitled Page</title>
</
head>
<
body>
    <form id="form1" runat="server">
        <div>
            <MS:MyTreeView runat="server" ID="MyTreeView1" ExpandDepth="2" DataSourceID="SiteMapDataSource1"
                OnTreeNodeDataBound="MyTreeView1_TreeNodeDataBound">
            </MS:MyTreeView>
            <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
                        
            
<MS:TemplateLibrary runat="server" ID="TemplateLibrary1">
                <MS:TemplateItem Name="arrow">
                    <Template>
                        ==>                        
                    
</Template>
                </MS:TemplateItem>
                <MS:TemplateItem Name="valuePath">
                    <Template>
                        ValuePath:
                        <%
# DataBinder.Eval(Container.DataItem, "ValuePath") %>
                    
</Template>
                </MS:TemplateItem>
            </MS:TemplateLibrary>
        </div>
    </form>
</
body>
</
html>
 
 
Web.SiteMap Contents:
 
<?xml version="1.0" encoding="utf-8" ?>
<
siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <
siteMapNode title="Home" preTemplate="arrow">
    <
siteMapNode title="Products" >
      <
siteMapNode title="Hardware" postTemplate="valuePath"/>
    </
siteMapNode>
  </
siteMapNode>    
</
siteMap>
 
Output:

Example 2: The key concept in this example is that the template library could be shared through a user control to every page that requires it. 

 
Templates.ascx:
<%@ Control Language="C#" ClassName="Templates" %>
<%
@ Register Namespace="MSSamples" TagPrefix="MS" %>

<script runat="server">
    public TemplateLibrary TLib
    {
        
get { return TemplateLibrary1; }
    }
</script>

        <MS:TemplateLibrary  runat="server" ID="TemplateLibrary1">
            <MS:TemplateItem Name="greenTemplate">
                <Template>
                    <div style="background-color:Green"><%# Eval("Text") %></div>
                </Template>
            </MS:TemplateItem>            
            
<MS:TemplateItem Name="yellowTemplate">
                <Template>
                    <div style="background-color:Yellow"><%# Eval("Text") %></div>
                </Template>
            </MS:TemplateItem>            
            
<MS:TemplateItem Name="redTemplate">
                <Template>
                    <div style="background-color:Red"><%# Eval("Text") %></div>
                </Template>
            </MS:TemplateItem>            
        
</MS:TemplateLibrary>
 
 
MyPage.aspx
 
<%@ Page Language="C#" %>
<%
@ Import Namespace="MSSamples" %>
<%
@ Register Src="Templates.ascx" TagName="Templates" TagPrefix="uc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<
script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    {
        
foreach( TemplateItem ti in Templates1.TLib.Templates )
        {
            
Menu m1 = new Menu();
            m1.Items.Add(
new MenuItem("Item One"));
            m1.Items.Add(
new MenuItem("Item Two"));
            m1.StaticItemTemplate = ti;
            form1.Controls.Add(m1);
        }
    }
</script>

<
html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
    <title>Untitled Page</title>
</
head>
<
body>
    <form id="form1" runat="server">
    <div>
        <uc1:Templates id="Templates1" runat="server">
        </uc1:Templates>
    </div>
    </form>
</
body>
</
html>
 
Output:
 
EDIT: One subtle note that I don't really mention in the article.  A large portion of DataBinding happens through reflection.  This really lets the Templates in the "library" maintain their ability to databind even though the container and data object isn't yet known.  This is cool because if two data objects happen to have the same property name, the same template could be used for either one.  I did assume that the container would be an IDataItemContainer, and that seems to work pretty well. 

Link to C# code listing

Customizing TreeNodes with RenderPreText and RenderPostText

Most of the ASP.NET 2.0 controls provide ways for the users to customize the look at feel of the output.  Some controls such as the Button have relatively little that can be customized while controls such as the DataList or Repeater allow the user nearly complete control over the rendering.  In some cases such as a Label this is presented through a series of properties that the user can set and in other cases, like Menu, this is done through templating. 

The TreeView offers a rather unique version.  The TreeNode object can be extended and used in place of the frameworks TreeNode.  In the object, two hooks are exposed that allow the developer to inject their own custom content.  These hooks are called "RenderPreText" and "RenderPostText".  They are enabled by creating an object which inherits from TreeNode and overriding these virtual functions.  Because they are virtual, the base TreeNode class will always call into the function defined in the subclass.

Here's a basic example that is pretty straight forward.  A bit of text is emitted from each handler to demonstrate their effect:

    // Basic overriding of the Pre/Post Text methods
    // This is the simplist form that enables a developer to
    // add custom content to the rendering of the TreeNode
    public class CustomTreeNode : TreeNode
    {
        
protected override void RenderPreText(HtmlTextWriter writer)
        {
            writer.Write(
" PRE TEXT ");
            
base.RenderPreText(writer);
        }

        
protected override void RenderPostText(HtmlTextWriter writer)
        {
            writer.Write(
" POST TEXT ");
            
base.RenderPostText(writer);
        }

    }
<asp:TreeView ID="TreeView1" runat="server">
    <Nodes>
        <My:CustomTreeNode Text="Node A"
                           Value="Node A">
            <My:CustomTreeNode Text="Node B"
                               Value="Node B">
            </My:CustomTreeNode>
        </My:CustomTreeNode>
    </Nodes>
</asp:TreeView>
 
 

You might imagine that there is a lot that can be accomplished through these methods.  Here are a couple examples I put together to demonstrate some various things that can be done:

Setting a per-node background image:

    // BG images for TreeNodes
    // This version adds two features.  
    // 1) it adds a specific property whose value is consumed
    // 2) it injects an additional control (<div>) into the rendering
    public class BGTreeNode : TreeNode
    {

        // This constructors is needed if a custom TreeView
        // instantiates this in CreateNode
        public BGTreeNode() : base() { }
        public BGTreeNode(TreeView owner, bool isRoot) :
           
base(owner, isRoot) { }

        
private string _bgImageUrl;
        
public string BackGroundImageUrl
        {
            
get { return _bgImageUrl;  }
            
set { _bgImageUrl = value; }
        }

        
protected override void RenderPreText(HtmlTextWriter writer)
        {
            writer.AddStyleAttribute(
               HtmlTextWriterStyle.BackgroundImage,
               "url('" + BackGroundImageUrl + "')");
            
//writer.AddStyleAttribute(
            //   HtmlTextWriterStyle.Height, "35px");
            //writer.AddStyleAttribute(
            //   HtmlTextWriterStyle.Width, "300px");
            writer.AddStyleAttribute(
               HtmlTextWriterStyle.TextAlign,
               "center");
            writer.RenderBeginTag(
HtmlTextWriterTag.Div );
            
base.RenderPreText(writer);
        }

        
protected override void RenderPostText(HtmlTextWriter writer)
        {
            writer.RenderEndTag();
            
base.RenderPostText(writer);
        }

    }
<asp:TreeView ID="TreeView2" runat="server">
    <Nodes>
        <My:BGTreeNode Text="Node A" Value="A"
                     BackgroundImageUrl="bg2.jpg" >
            <My:BGTreeNode Text="Node B" Value="B"
                     BackgroundImageUrl="bg3.jpg">
            </My:BGTreeNode>
        </My:BGTreeNode>
    </Nodes>                
</asp:TreeView>

Creating individual TreeNode CSS styles:

    // CssClasses per TreeNode
    // This is a more general form of the
    // background image treeNode.  This would enable
    // invidividual customization of TreeNodes
    public class CSSTreeNode : TreeNode
    {

        // This constructors is needed if a custom TreeView
        // instantiates this in CreateNode
        public CSSTreeNode() : base() { }
        public CSSTreeNode(TreeView owner, bool isRoot) :
           
base(owner, isRoot) { }

 
        private string _cssClass;
        
public string CssClass
        {
            
get { return _cssClass; }
            
set { _cssClass = value; }
        }

        
protected override void RenderPreText(HtmlTextWriter writer)
        {
            writer.AddAttribute(
               HtmlTextWriterAttribute.Class, CssClass);
            writer.RenderBeginTag(
HtmlTextWriterTag.Div);
            
base.RenderPreText(writer);            
        }

        
protected override void RenderPostText(HtmlTextWriter writer)
        {
            writer.RenderEndTag();
            
base.RenderPostText(writer);
        }

    }
<asp:TreeView ID="TreeView3" runat="server">
    <Nodes>
        <My:CssTreeNode Text="Node A" Value="Node A"
                        CssClass="nodea" >
            <My:CssTreeNode Text="Node B"  Value="Node B"
                            CssClass="nodeb" >
            </My:CssTreeNode>
        </My:CssTreeNode>
    </Nodes>
</asp:TreeView>  

And the ultimate - Templating the PreText/PostText content

Update:  One issue I found when using this was that the template needs to be repeatedly assigned.  If you need any kind of reuse, there's a follow up technique: http://weblogs.asp.net/dannychen/archive/2006/01/27/436714.aspx

    // Templated PreText and PostText of a TreeNode
    // This enables individual TreeNodes to be able to Template some of their contents.
    // Done this way, it is a per-node basis meaning that each node that is to be templated
    // would need the markup for the template it it's declaration.
    [ParseChildren(false)]
    
public class TemplatedTreeNode : TreeNode
    {

        // This constructors is needed if a custom TreeView
        // instantiates this in CreateNode
        public TemplatedTreeNode() : base() { }
        public TemplatedTreeNode(TreeView owner, bool isRoot) :
           
base(owner, isRoot) { }

        
private ITemplate _preTextTemplate;
        [
PersistenceMode(PersistenceMode.InnerProperty),
        
TemplateContainer(typeof(TreeNodeTemplateContainer))]
        
public ITemplate PreTextTemplate
        {
            
get { return _preTextTemplate; }
            
set { _preTextTemplate = value; }
        }

        
private ITemplate _postTextTemplate;
        [
PersistenceMode(PersistenceMode.InnerProperty),
        
TemplateContainer(typeof(TreeNodeTemplateContainer))]
        
public ITemplate PostTextTemplate
        {
            
get { return _postTextTemplate; }
            
set { _postTextTemplate = value; }
        }

        
protected override void RenderPreText(HtmlTextWriter writer)
        {
            
if (PreTextTemplate != null)
            {
                
TreeNodeTemplateContainer container =
                  new TreeNodeTemplateContainer(this);
                PreTextTemplate.InstantiateIn(container);
                container.DataBind();
                container.RenderControl(writer);
            }
            
base.RenderPreText(writer);
        }

        
protected override void RenderPostText(HtmlTextWriter writer)
        {
            
if (PostTextTemplate != null)
            {
                
TreeNodeTemplateContainer container =
                  new TreeNodeTemplateContainer(this);
                PostTextTemplate.InstantiateIn(container);
                container.DataBind();
                container.RenderControl(writer);
            }            
            
base.RenderPostText(writer);
        }

    }

    
// Template container for the TemplatedTreeNode class
    public class TreeNodeTemplateContainer : WebControl, IDataItemContainer
    {
        
private TreeNode _node;
        
public TreeNode Node { get { return _node; } }

        
public TreeNodeTemplateContainer(TreeNode n)
        {
            _node = n;
        }

        #region IDataItemContainer Members

        
public object DataItem
        {
            
get { return Node; }
        }

        
public int DataItemIndex
        {
            
get { return 0; }
        }

        
public int DisplayIndex
        {
            
get { return 0; }
        }

        #endregion

    }
<asp:TreeView ID="TreeView4" runat="server">
    <Nodes>
        <My:TemplatedTreeNode Text="Node A"
                       Value="Node A" >                        
            
<PreTextTemplate>
                Text: <%# Databinder.Eval(Container.DataItem, "Text") %>
            
</PreTextTemplate>
            <PostTextTemplate>
                Value: <%#DataBinder.Eval(Container.DataItem, "Value")%>
            
</PostTextTemplate>
            <ChildNodes>
                <My:TemplatedTreeNode Text="Node B"
                        Value="Node B" >
                </My:TemplatedTreeNode>
            </ChildNodes>
        </My:TemplatedTreeNode>
    </Nodes>
</asp:TreeView>

There are a couple caveats of doing this.  The most obvious one you'll notice is that simplified databinding doesn't work.  Simplified databinding needs the controls to exist in the control hierarchy of the page.  Since TreeNodes aren't technically controls and don't exist in the control hierarchy of the page, they can't take advantage of simplified databinding.  However, as in my example, "old school" databinding still works.  Secondly, and less obvious, templates are normally instantiated in CreateChildControls which is much earlier in the lifecycle than Render where these templates are being instantiated.  Because of this, there are some quirks such as Databinding events for the templated controls being fired in Render instead of PreRender.  Just be aware that these added templates may not act 100% like templates that were designed into a control but they should work pretty well. 

Hopefully these examples will give you some ideas of what can be accomplished with Pre/Post Text.  One thing you'll quickly figure out when you try this is that you don't have much intellisense for your custom TreeNodes.  This is because intellisense is only enabled for objects in the same namespace.  Since the TreeView and the TreeNodes are in different namespaces, the intellisense hookup doesn't work.  If this is a big issue, you can create a custom TreeView in the same namespace as your custom tree nodes and use that TreeView instead.  It's not a necessity.

footnote:  if this wasn't enough for you, there's no reason why you couldn't mix your custom node types in the same TreeView either.....

EDIT:  Bertrand pointed out below that you would need a custom TreeView to utilize these custom nodes for a DataBound kind of scenario.  Heres an example of how that would work:

Custom TreeView Code:
 
public class MyTreeView : TreeView
{
    
protected override TreeNode CreateNode()
    {
        
return new CSSTreeNode(this, false);
    }
}
 
Plus Some Markup:
 
<My:MyTreeView runat="server" ID="MyTreeView1"
    
DataSourceID="SiteMapDataSource1"
    
OnTreeNodeDataBound="MyTreeView1_TreeNodeDataBound">
</My:MyTreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
 
Plus an Event Handler:
 
<script runat="server">
protected void MyTreeView1_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
{
    
if (e.Node is CSSTreeNode)
    {
        
CSSTreeNode tn = e.Node as CSSTreeNode;
        tn.CssClass = ((
SiteMapNode)e.Node.DataItem)["style"];
    }            
}
</script>
 
Equals:
Dealing with concurrency issues in custom SiteMapProviders

I spend a lot of time on the ASP.NET forums.  Hopefully my efforts over there help steer users into the right direction of how to use my features and ASP.NET properly and most effectively.  But sometimes I see some really alarming things.  Recently, the most scary thing I've seen is some quite improperly written SiteMapProviders.  Unfortunately, I'm not saying there are just plain buggy or wrongly implemented.  Those are the obvious kinds of issues that the developers will find easily through testing and don't really bother me that much.  What does bother me is code that will only fail when it's deployed and stressed. 

Here's an example, what's wrong with the following code:

' BAD CODE EXAMPLE
Public Class BadProvider
    
Inherits StaticSiteMapProvider

    
Private _rootNode As SiteMapNode

    
Public Overrides Function BuildSiteMap() As SiteMapNode
        
If _rootNode Is Nothing Then            
            AddNode(newNode1,
Nothing)
            AddNode(newNode2, newNode1)
            AddNode(newNode3, newNode1)

            _rootNode = newNode1
        
End If

        Return _rootNode
    
End Function

    Protected Overrides Function GetRootNodeCore() As SiteMapNode
        
Return BuildSiteMap()
    
End Function

    Protected Overrides Sub Clear()
        _rootNode =
Nothing
        MyBase.Clear()
    
End Sub

    Protected Sub MyInvalidationMethod()
        Clear()
    
End Sub
End
Class

Give up yet?  Simply and generally stated, the problem is that it's not Thread Safe Why not?  Well, perhaps this is the hardest concept for users to either grasp or accept about the Provider Model.  Providers are instantiated as a single instance across the app domain.  This means a single provider is shared by all of the worker processes (ie all the requests) that come into the site. 

In this example, one particular request triggers the Clear() event while the others all query data.  Each query to data implicitly calls into BuildSiteMap out of necessity.  There are several potential problems here:

   1) A race condition between BuildSiteMap and Clear trying to modify the data at the same time
   2) Simultaneous threads executing the BuildSiteMapCode at the same time
   3) A race condition between BuildSiteMap, Clear, and the calling page performing future accesses

These are not new issues presented by the SiteMapProvider model, these are fundamental issues that need to be dealt with in any multithreaded design.  Ok, I've talked the talk, how do I walk the walk.  With ASP.NET, it's relatively easy.  Simply use the lock(object) (C#) or SyncLock object (VB) features.  The important part is to know what to lock.  The StaticSiteMapProvider protects all of it's APIs but anytime an API is overridden that can modify the data, it needs to be protected.  This means:  AddNode, RemoveNode, Clear, and BuildSiteMap.  Here's the previous example properly coded.  If AddNode or RemoveNode needs to be overriden, they should be overridden the same way Clear has been.

Public Class GoodProvider
    
Inherits StaticSiteMapProvider

    
Private lockObj As New Object
    Private _rootNode As SiteMapNode

    Public Overrides Function BuildSiteMap() As SiteMapNode
        
Dim _tempRoot As SiteMapNode = _rootNode

        
If _tempRoot IsNot Nothing Then
            Return _tempRoot
        
End If

        SyncLock lockObj
            
If _rootNode Is Nothing Then
                MyBase.Clear()
                AddNode(newNode1,
Nothing)
                AddNode(newNode2, newNode1)
                AddNode(newNode3, newNode1)

                _rootNode = newNode1
            
End If
            Return _rootNode
        
End SyncLock
    End Function


    Protected Overrides Function GetRootNodeCore() As SiteMapNode
        
Return BuildSiteMap()
    
End Function

    Protected Overrides Sub Clear()
        
SyncLock lockObj
            _rootNode =
Nothing
        End SyncLock
    End Sub

    Protected Sub MyInvalidationMethod()
        Clear()
    
End Sub
End
Class

public class GoodProvider : StaticSiteMapProvider
{
    
private object _lockObj = new object();
    
private SiteMapNode _rootNode;

    public override SiteMapNode BuildSiteMap()
    {
        
SiteMapNode _tempRoot = _rootNode;

        
if (_tempRoot != null)
            
return _tempRoot;

        
lock (_lockObj)
        {
            
if (_rootNode == null)
            {
                
base.Clear();
                AddNode(newNode1,
null);
                AddNode(newNode2, newNode1);
                AddNode(newNode3, newNode1);

                _rootNode = newNode1;
            }
            
return _rootNode;
        }
    }


    
protected override SiteMapNode GetRootNodeCore()
    {
        
return BuildSiteMap();
    }

    
protected override void Clear()
    {
        
lock (_lockObj)
        {
            _rootNode =
null;
        }
    }

    
protected void MyInvalidationFunction()
    {
        Clear();
    }

}

One thing to look more closely at in this example is the BuildSiteMap method.  As I previously mentioned, all the data reading APIs of the SiteMapProvider call into BuildSiteMap to ensure the provider data has been populated.  In short, it gets called a lot.  Because of this, it's important that the primary flow of that function not be locked and only the creation portion does.

Update 1/12/2006: I was alerted to a couple remaining race conditions in my good code so I've updated BuildSiteMap to eliminate them.  Thanks Joe for looking at this.

Rendering a databound UL menu

One very common request I get is how to render the Menu with <UL>'s and <LI>'s instead of the heavy table rendering.  Even though the Menu allows for templating, most users quickly find that this cannot be solved with just templating.  Perhaps we didn't expect this or we didn't think it was important enough at the time; I'm not sure, the decision was made long before me.  However, whatever the reason, the Menu doesn't support this kind of rendering.  But, of course, since you're reading this, there are several solutions...   

A simple UL menu

The simplest solution for a single level of hierarchy is to simply use a Repeater control bound to your SiteMapDataSource:

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="False" />

<asp:Repeater ID="Repeater1" runat="server" DataSourceID="SiteMapDataSource1">
    <HeaderTemplate><ul></HeaderTemplate>
    <ItemTemplate><li><%# Eval("title") %></li></ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

It's a pretty straight forward solution.  It's fairly flexible as well.  Templating and Databinding are availible so custom styling of individual items should be easily possible. 

The next obvious improvement is to allow some hierarchy information.  This can be done with nesting the Repeaters.

<asp:SiteMapDataSource ID="SiteMapDataSource2" runat="server" ShowStartingNode="False" />
<asp:Repeater ID="Repeater2" runat="server" DataSourceID="SiteMapDataSource2">
    <HeaderTemplate><ul></HeaderTemplate>
    <ItemTemplate><li><%# Eval("title") %>
        
<asp:SiteMapDataSource ID="SiteMapDataSource2_1" runat="server"
                               ShowStartingNode="False" StartingNodeUrl='<%# Eval("url") %>' />
        <asp:Repeater ID="Repeater3" runat="server" DataSourceID="SiteMapDataSource2_1">
            <HeaderTemplate><ul></HeaderTemplate>
            <ItemTemplate><li><%# Eval("title") %></li></ItemTemplate>
            <FooterTemplate></ul></FooterTemplate>
        </asp:Repeater>                
        
</li>
    </ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

Again this works fairly well and doesn't use any code.  But there are a couple obvious issues with this approach.  First, although it provides for hierarchy, it's still limited to a fixed depth.  Second, if a node has no child nodes, the code will still render and empty <ul></ul> tag.

A custom, templated HierarchicalDataBoundControl

Ok, so writing this blog entry was just an excuse for me to have a little fun and come up with this sample.  But, the way to get the most flexible rendering is a custom control.  This is also a good example of creating a templated control and implementing a HierarchicalDataBound control. 

Implementing Templates and Databinding

Actually templates aren't new, they've been around for a while.  Using them is pretty straight forward.  Declare a template property using the ITemplate interface and instantiate it with InstantiateIn(container).

Private _HeaderTemplate As ITemplate

<PersistenceMode(PersistenceMode.InnerProperty), _
 TemplateContainer(GetType(MyDataItem))> _
Public Property HeaderTemplate() As ITemplate
    
Get
        Return _HeaderTemplate
    
End Get
    Set(ByVal value As ITemplate)
        _HeaderTemplate = value
    
End Set
End Property

Protected Overrides Sub CreateChildControls()
    Dim header As New MyDataItem(String.Empty)
    HeaderTemplate.InstantiateIn(header)
    Controls.Add(header)
End Sub


 

What is new is the IDataItemContainer interface.  This new interface is designed to make it easier to databind using Eval(<prop>).  The interface has 3 members:

Public ReadOnly Property DataItem() As Object Implements IDataItemContainer.DataItem
Public ReadOnly Property DataItemIndex() As Integer Implements IDataItemContainer.DataItemIndex
Public ReadOnly Property DisplayIndex() As Integer Implements IDataItemContainer.DisplayIndex

The DataItemIndex and DisplayIndex properties are used to keep track of the index of the current item and it's index in the rendering.  This can be useful for controls that do paging.  However, if you aren't paging, you can cheat (like I did) and return a dummy value.  The DataItem property is the most interesting one.  It's the object that is returned from an operation like:  <%# Container.DataItem %>  The way this ties into templates is that the template container should implement the IDataItemContainer interface. 

    Public Class MyDataItem
        
Inherits Control
        
Implements INamingContainer
        
Implements IDataItemContainer

        
Private _data As MyDataObject

        Public ReadOnly Property DataItem() As Object Implements UI.IDataItemContainer.DataItem
            
Get
                Return _data
            End Get
        End Property

        Public ReadOnly Property DataItemIndex() As Integer Implements IDataItemContainer.DataItemIndex
            
Get
                Return 0
            
End Get
        End Property

        Public ReadOnly Property DisplayIndex() As Integer Implements IDataItemContainer.DisplayIndex
            
Get
                Return 0
            
End Get
        End Property
    End Class

 

Implementing a HierarchicalDataBoundControl

Implementing a HierarchicalDataBoundControl is a lot like any other custom control.  The best place to override is CreateChildControls.  A common design pattern is to create a private recursive CreateChildControls and call into it from CreateChildControls.  The biggest feature that the HierarchicalDataBoundControl base class gives us is GetData(string) and GetDataSource().  These two provide access to a HierarchicalDataSourceView which in turn provides access to the hierarchy data through IHierarchicalEnumerable and IHierarchyData objects.  You can see more information about those interfaces in this previous post and at the links at the end of the article. 

 

        Protected Overrides Sub CreateChildControls()
            RecursiveCreateChildControls(GetData(
"").Select())
        
End Sub

        Private Sub RecursiveCreateChildControls(ByVal dataItems As IHierarchicalEnumerable)
            For Each e As Object In dataItems

                Dim data As IHierarchyData = dataItems.GetHierarchyData(e)

                
Dim text As String = String.Empty
                text = DataBinder.GetPropertyValue(data, TextField)
 
                ... add controls ...
 
                RecursiveCreateChildControls(data.GetChildren())

            Next
        End Sub

One other extremely useful class is the DataBinder class.  Recall from the IDataItemContainer interface that the data is just an object reference.  It may be an xmlElement or a SiteMapNode or a DataRowView or some other object.  In order to extract property values from an arbitrary object, the DataBinder.GetPropertyValue(object, string) function can be used.  Putting together all these concepts and we have a HierchicalDataBoundControl that supports templates and databinding.  The complete code listing for this custom control is availible at the end of this post.  Here's how it can be used for a <UL> <LI> menu.  The exact same code can also be bound to a XmlDataSource.

<asp:SiteMapDataSource ID="SiteMapDataSource3" runat="server" />

<My:RecursiveULMenu ID="rl1" DataSourceID="SiteMapDataSource3"
                    
runat="server" TextField="title">
    <HeaderTemplate><ul></HeaderTemplate>
    <FooterTemplate></ul></FooterTemplate>
    <ItemHeaderTemplate><LI></ItemHeaderTemplate>
    <ItemTemplate>
        <%# Eval("Text") %>    
    
</ItemTemplate>
    <ItemFooterTemplate></LI></ItemFooterTemplate>
</My:RecursiveULMenu>
                  

<asp:XmlDataSource ID="XmlDataSource1" runat="server" DataFile="XMLFile.xml" />

<My:RecursiveULMenu ID="RecursiveList1" DataSourceID="XmlDataSource1"
                    
runat="server" TextField="name">
    <HeaderTemplate><ol></HeaderTemplate>
    <FooterTemplate></ol></FooterTemplate>            
    
<ItemHeaderTemplate><LI></ItemHeaderTemplate>
    <ItemTemplate>
        <%# Eval("Text") %>    
    
</ItemTemplate>
    <ItemFooterTemplate></LI></ItemFooterTemplate>
</My:RecursiveULMenu>

References:

http://msdn2.microsoft.com/library/system.web.ui.idataitemcontainer.aspx
http://msdn2.microsoft.com/library/system.web.ui.webcontrols.hierarchicaldataboundcontrol.aspx
http://msdn2.microsoft.com/en-us/library/system.web.ui.itemplate.aspx
http://msdn2.microsoft.com/library/system.web.ui.ihierarchicalenumerable.aspx
http://msdn2.microsoft.com/library/system.web.ui.ihierarchydata.aspx

Code Listings:

Link to VB code listing
Link to C# code listing

 

UPDATE 1/6/06:

  As found by Eric in the comments below, the code gets wrapped in a SPAN.  It's possible that you might want to wrap the control with a DIV instead.  In that case you can override RenderBeginTag like so:

Public Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter)
    writer.AddAttribute(HtmlTextWriterAttribute.Id,
Me.ClientID)
    writer.RenderBeginTag(HtmlTextWriterTag.Div)
End Sub
 
 or
 
public override void RenderBeginTag(System.Web.UI.HtmlTextWriter writer) {
    writer.AddAttribute(
HtmlTextWriterAttribute.Id, this.ClientID);
    writer.RenderBeginTag(
HtmlTextWriterTag.Div);
}
More Posts Next page »