Uncategorized

Very slow TreeView in UpdatePanel

This is what we got couple of weeks ago: an Ajax enabled ASP.NET web application was using a TreeView control within an UpdatePanel to show a complex tree of hierarchical data (the sample we got had more than 2.000 nodes with varying degrees of nesting). The result was that browsing the tree within IE was considerably slower if compared with Firefox, about 4 times (where Firefox took 2 seconds to complete the operation, IE took 8 seconds a and sometimes a bit more).

With a repro in our hands, a network trace demonstrated that the communication between client and server was working fine (the problem reproduced also on localhost); while having the problem IE was burning a lot of CPU and the relevant thread had a callstack link this:

kernel32!TlsGetValue+0xb 
jscript!GcContext::GetGc+0xc
jscript!NameTbl::FBaseThread+0xb 
jscript!NameTbl::GetVal+0xe 
jscript!VAR::InvokeByName+0x10d 
jscript!VAR::InvokeDispName+0x43 
jscript!VAR::InvokeByDispID+0xb9 
jscript!CScriptRuntime::Run+0x167f 
jscript!ScrFncObj::Call+0x8d 
jscript!NameTbl::InvokeInternal+0x40 
jscript!VAR::InvokeByDispID+0xfd 
jscript!VAR::InvokeByName+0x165 
jscript!VAR::InvokeDispName+0x43 
jscript!VAR::InvokeByDispID+0xb9 
jscript!CScriptRuntime::Run+0x167f 
jscript!ScrFncObj::Call+0x8d 
jscript!NameTbl::InvokeInternal+0x40 
jscript!VAR::InvokeByDispID+0xfd 
jscript!VAR::InvokeByName+0x165 
jscript!VAR::InvokeDispName+0x43 
jscript!VAR::InvokeByDispID+0xb9 
jscript!CScriptRuntime::Run+0x167f 
jscript!ScrFncObj::Call+0x8d 

[...]

mshtml!CBase::InvokeEvent+0x1ad 
mshtml!CBase::FireEvent+0x105 
mshtml!CXMLHttpRequest::Fire_onreadystatechange+0x5b 
mshtml!CXMLHttpRequest::CReadyStateSink::Invoke+0x1d 
[...]

The code behind that was quite simple:

protected void onSelectNodeChange(object sender, EventArgs e)
{
    Session["selectedNode"] = TreeView1.SelectedNode.ValuePath;
    Session["idFirstTree"] = TreeView1.SelectedNode.Value;

    UpdatePanel2.Update();
}

The problem is that the hole tree is destroyed and recreated at every iteration (Sys$WebForms$PageRequestManager$_destroyTree in MicrosoftAjaxWebForms.js); the method traverses the whole DOM tree, iterates through and releases them. The tree is actually made of a table with many TableRows and TableCells and many of those cells have nested DIVs as for example

<TD>
    <DIV style="WIDTH: 20px; HEIGHT: 1px"></DIV>
</TD>
<TD>
    <DIV style="WIDTH: 20px; HEIGHT: 1px"></DIV>
</TD>
<TD>
    <DIV style="WIDTH: 20px; HEIGHT: 1px"></DIV>
</TD>
<TD>
    <DIV style="WIDTH: 20px; HEIGHT: 1px"></DIV>
</TD>
<TD>
    <A id=ctl00_TreeView1n121 href="javascript:TreeView_ToggleNode(ctl00_TreeView1_Data,121,ctl00_TreeView1n121,' ',ctl00_TreeView1n121Nodes)">
    <IMG style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" alt="Expand Note" src="/MyApp/WebResource.axd?d=poTVB8BI5WzFAOGATmVHQ92ltsYfnmkuDjQn4Ufor001&amp;t=633363364222850001">
    </A>
</TD>
<TD class="ctl00_TreeView1_2 ctl00_TreeView1_4" onmouseover="TreeView_HoverNode(ctl00_TreeView1_Data, this)" style="WHITE-SPACE: nowrap" onmouseout=TreeView_UnhoverNode(this)>
    <A class="ctl00_TreeView1_0 ctl00_TreeView1_1 ctl00_TreeView1_3" id=ctl00_TreeView1t121 title="Node one \ Subnode one \ Subnode two \ Subnode three \ Leaf" onclick="TreeView_SelectNode(ctl00_TreeView1_Data, this,'ctl00_TreeView1t121');" href="javascript:__doPostBack('ctl00$TreeView1','5\\6\\7\\8\\9')">Select
    </A>
</TD>

This is a very CPU intensive operation in the mshtml model, and unfortunately the IE script and HTML engine have some troubles and performance problems in some scenarios… anyway this is not one of them. The point is that destroy and recreate the content of an UpdatePanel is normal (this is how Ajax works), what’s wrong is that the entire tree is destroyed and recreated thousands of times…

The TreeView control uses some scripts injected by ASP.NET but those are in conflict with the uses used by Ajax for the UpdatePanel and this is what origins the sort of recursion we saw in the dump. From a supportability point of view this is a known issue and one of the reasons because the TreeView control is incompatible and not supported when hosted within an UpdatePanel:

The following ASP.NET controls are not compatible with partial-page updates, and are therefore not designed to work inside an UpdatePanel control:

· TreeView control under several conditions. One is when callbacks are enabled that are not part of an asynchronous postback. Another is when you set styles directly as control properties instead of implicitly styling the control by using a reference to CSS styles. Another is when the EnableClientScript property is false (the default is true). Another is if you change the value of the EnableClientScript property between asynchronous postbacks. For more information, see TreeView Web Server Control Overview.

(from http://msdn2.microsoft.com/en-us/library/bb386454.aspx)

A TreeView is always completely rendered in HTML and sent to the client. Depending on the ExpandedState, it sets only the parts visible that should to be seen initially and leaves the rest invisible; this is done by setting the display style of the DIVs and other tags which are part of the tree structure.

  • Without the UpdatePanel (i.e. without using Ajax) here’s what happens:
    • When EnableClientScript=”true” then the TreeView client site script expands/collapses the nodes; this is done calling TreeView_ToggleNode(), setting the display stile to block and updating some status values like ExpandedState. This makes the relevant parts of the tree visible without requesting data from the server. A request is sent to the server only when a node is selected; in such a case the whole tree is sent back to the server with the selected node’s style modified accordingly.
    • With EnableClientScript=”false” on every expand/collapse operation the currently visible part of the tree is sent back to the server
  • When using a TreeView inside an UpdatePanel:
    • When EnableClientScript=”true” then the TreeView client side script expands/collapses the nodes like happens in the case without Ajax. When a node is selected then the whole content of the UpdatePanel (which is the whole TreeView server control) is posted back to the server with the selected node’s style modified
    • With EnableClientScript=”false” the server renders only the visible part of the tree and sends it to the client, i.e. the server only send to the client the markup needed to display the visible part of the tree. Every time a node is expanded, and Ajax request is sent to the server to get the portion of the tree to be displayed; this partial tree is what is sent back to the server with an Ajax postback (the already visible tree part plus the newly expanded tree).

Conclusion

In this specific scenario we set EnableClientScript=”false” for the TreeView control and let the Ajax scripts deal with the dynamic behaviors, and that is enough as long as you don’t need specific behaviors you get only through the TreeView client-side scripts. Bottom line: test very carefully this scenario since it might work but it’s still not supported (and not tested by Microsoft) therefore something might suddenly go wrong and we would have very limited possibilities to help you effectively…

 

P.s.
Thanks to Stefano Pronti and Karl Tietze for sharing this one.

 

Carlo

Quote of the day:
We learn something every day, and lots of times it’s that what we learned the day before was wrong. – Bill Vaughan

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.