ASP.NET 2.0 and MCMS – Site Navigation – Part 2

In the first part of this article I discussed how to implement a SiteMapProvider for an MCMS website.

ASP.NET 2.0 ships with three new controls that can be used for site navigation:

  • SiteMapPath
  • Menu
  • TreeView

Problems with the TreeView control

SiteMapPath and the Menu control can easily be used on a MCMS website but the TreeView control is implemented in a way which causes problems with MCMS. The reason for these problems are the fact that the TreeView control is able to populate nodes on demand. What does this mean?

Populate on demand is highly useful especially for large sites! If this feature would not be available then the treeview would always be prepopulated with the whole tree structure – means with all nodes in the tree which can be the whole channel structure in MCMS. Enumerating the whole channel structure – which would be required without populate on demand – can take a very long time and can bring the overall server performance down quickly.

Have a look at the following picture:

Here the TreeView control shows only the Root Level and one level below. The Control only enumerated the root channel and the first level of included channels and the SiteMapProvider was also only called for these 4 nodes.

If a user now clicks on one of the “+” signs (e.g. for the Development channel) the tree view control needs to get the additional data for the nodes inside the Development channel. To do this the TreeView control does a postback to the server to request the additional nodes and after the postback the result will then look as follows:

In the last beta I worked with this postback was a normal http postback as you will know it from your own web forms. The caveat with this approach is that the whole screen gets refreshed rather than only the piece inside the Development node.

So for the final release the design was changed and now a new technique included in ASP.NET 2.0 does a silent postback from Javascript using the Microsoft.XMLHTTP COM object. This technique is known as AJAX (Asynchronous Javascript And XML).

This method works perfectly fine on all normal ASP.NET webforms – but unfortunatelly not on MCMS templates or channel rendering scripts. The reason for the problem is that the AJAX implementation in ASP.NET uses the content of the Action property of the current web form – and ASP.NET always renders the a relative link in the action property and not an absolut link starting from the root of the website. Something like this: template.aspx?NRMODE=Published&NRNODEGUID=…

A technique that work perfectly fine for normal ASP.NET pages as the URL in the browser is identical to the URL ASP.NET knows – but a technique that does not work correct with MCMS as MCMS serves friendly URLs to the browser which are rewritten by the ISAPI filter to the ugly URLs. So the browser knows a URL like http://servername/Development/ while the “real” URL for ASP.NET looks as follows: http://servername/templateproject/template.aspx?NRMODE=Published&NRNODEGUID=…

If AJAX does its postback using XMLHTTP it expects to hit the template.aspx file inside the current directory. But for the browser the current directory is inside the /Development channel and not within the templateproject virtual directory. So the browser send the request to a wrong location. You can easily proof this by having a look into your IIS log!

Normal postbacks do usually not experience this problem as MCMS injects some javascript code to correct the URL right before doing a postback to the posting. But this code is not executed from the ASP.NET AJAX code.

A second problem coming up with these AJAX postbacks is that they raise the “Are you sure you want to navigate away from this page?” message when clicking on one of the nodes to expand or collapse the treeview while being in edit mode.

Solution

So how can we address these problems?

The first problem can be addressed if we can modify the action property of the form tag before the user clicks on the node to populate the content of the next level. Code like the following will do this:

    <script>
        if (typeof (__CMS_CurrentUrl) != “undefined”)
        {
                __CMS_PostbackForm.action = __CMS_CurrentUrl;
        }
    </script>

The second problem is a little bit more complicated. I earlier outlined a method to address this problem for the ASP.NET 1.1 controls but unfortunatelly this method fails – again due to the fact that AJAX is used to modify the html content on the client side. So we need to use a method which executes the content of the href attribute without actually executing the href itself.

Looking into the browser event model there is a solution for this: the onclick event is fired before the content of the href attribute is accessed. So if we manage to execute the javascript code in the href attribute from inside the onclick event and if we manage to prevent the execution of the href attribute the problem will not show up.

To execute the javascript code stored in the href we can use the eval javascript function. And to prevent the href attribute from being executed we need to return “false” from the onclick event handler as this will cancel the user action.

Introducing the MCMSTreeView control

As an implementation that addresses both problems I decided to implement a custom TreeView control which is derived from the ASP.NET 2.0 TreeView control. This control will run fine on normal ASP.NET pages and on MCMS templates and channel rendering scripts:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;

namespace StefanG.ServerControls
{
    public class MCMSTreeView : TreeView
    {
        protected override void Render(System.Web.UI.HtmlTextWriter output)
        {
            // catch the output of the original HtmlPlaceholderControl
            TextWriter tempWriter = new StringWriter();
            base.Render(new System.Web.UI.HtmlTextWriter(tempWriter));
            string orightml = tempWriter.ToString();

            // search for the javascript postback code in the href attribute and if found add 
            // an onclick event handler to execute it in a way that does not cause the 
            // MCMS authoring warning to show up
            Regex hrefRegex = new Regex(“href=\”javascript:(?<PostBackScript>.*?)\””, RegexOptions.Singleline);
            string newhtml = hrefRegex.Replace(orightml, 
                                “href=\”javascript:${PostBackScript}\” onclick=\”eval(this.href.substr(11));return false;\””);

            output.Write(newhtml);

            // as a final step register a client script that ensures that the action property
            // is correct when AJAX tries to do its postback.
            string script =
                “\n<script type=\”text/javascript\”>\n” +
                ”   if (typeof (__CMS_CurrentUrl) != \”undefined\”)\n” +
                ”   {\n” +
                ”       __CMS_PostbackForm.action = __CMS_CurrentUrl;\n” +
                ”   }\n” +
                “</script>\n”;

            Page.ClientScript.RegisterStartupScript(this.GetType(), “AdjustActionForTreeView”, script);
        }

    }
}

10 Comments


  1. I found that the GetChildNodes method was being called for an extra level down in addition to what you mentioned?

    Has anyone created a treeview navigation using SiteMapProvider which does not rely on javascript? The EnableClientScript setting on the treeview control just turns off the AJAX functionality, javascript is required for the full postback mode.

    For accessibility reasons it is necessary to be able to provide navigation with javascript turned off.

    Reply

  2. I have posted a solution for this problem a subsection of this article but it someone just looking on…

    Reply

  3. Now after MCMS 2002 SP2 has been released I would like to point you again to an article series I have…

    Reply

  4. Does this issue also exist with the menu control?

    Reply

  5. Hi Chandy,

    usually the menu control does not do any postbacks to populate the items.

    Cheers,

    Stefan

    Reply

  6. Hi Stefan

    Thanks for the nice article. I used the MCMSTreeView class to generate Sitemap. Now it is live.

    But m facing weired issue with it. Some time the treeview shows javascript error "Object required" and also that time not allow to expand any node with another javascript error “expandState is null or not an object”.

    After some analysis and research, we got that this is related to the javascript generated for Treeview control. There is an http handler called WebResources.axd in .net 2.0, which generates javascript functions required by particular page. The problem is with how these .axd extensions are mapped in IIS.

    Then we were able to reproduce it on dev machine by checking the “Verify that file exists” check box of the site’s IIS Configuration setting against .axd application extension.  

    But we check all loadbalanced Server’s “Verify that file exists” check box on production, it was already unchecked.

    At last that solution didn’t work.

    Can you please help me???

    Reply

  7. Hi Niki,

    sorry I have never seen this. But if it is a problem with the AXD references, then it is an ASP.NET issue and not a MCMS issue.

    So you might want to follow up with an ASP.NET expert.

    Cheers,

    Stefan

    Reply

  8. Hi, I am creating a navigation for MCMS based multilingual website using asp.net menu but I am confused about how to bind the Menu, I can use three options which are mentioned below also I have to allow the user to sort those menus as well:

    1)Using a hardcoded XML file with Multi Language Menu in Nodes.

    2)Using a hardcoded resource file with multiplie languages.

    3)At run time using the MCMS Channel names and creating one Navigation Channel and binding it back to the ASP.NET menu.

    Which would be the best Option to bind such a navigational Menu.

    Regards

    Ankit Srivastava

    ankit.sri@hotmail.com

    Reply

  9. Hi Ankit,

    you should use a custom SiteMapProvider as discussed in the first part of this article series.

    Cheers,

    Stefan

    Reply

  10. Hi,

    I used the code you provided, but it only fixes the problem for the top-level node. I believe that for the other nodes, you need to do the same trick in the GetCallbackResult method.

    To do so, add the following code:

           protected override string GetCallbackResult()

           {

               string orightml = base.GetCallbackResult();

               // search for the javascript postback code in the href attribute and if found add  

               // an onclick event handler to execute it in a way that does not cause the  

               // MCMS authoring warning to show up

               Regex hrefRegex = new Regex("href="javascript:(?<PostBackScript>.*?)"", RegexOptions.Singleline);

               string newhtml = hrefRegex.Replace(orightml,

                                   "href="javascript:${PostBackScript}" onclick="eval(this.href.substr(11));return false;"");

               return newhtml;

           }

    Regards

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.