in

ASP.NET Weblogs

-[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)