May 27th 2008 05:45 pm
Optimize UpdatePanel performance by avoiding unnecessary element disposal
I fought using update panels for a long time. Every time someone demonstrates the MS AJAX framework they skip the amazing features and go right to update panels. They’d say “Look how easy they are to use!”, “They integrate right into the code you have now.”, and my personal favorite, “This is how you can AJAX enabled your website”. I thought they were a heavy solution filled with problems like smartnav has in asp.net 1.1.
The MS AJAX framework is much more than just update panels. There’s more to writing AJAX enabled web sites than wrapping your dynamic content inside update panels. However, there’s something I failed to realize- for what they do, update panels are the best tool for the job. If the goal is to asynchronously update content in a page using the same code used to render the content in the first place, updates panels are it. Less obvious features include:
- Only the data being refreshed is transmitted from the web server
- UI code uses the same controls to render synchronously or asynchronously
- The framework manages the loading of additional javascript and css references
- The framework only renders update panels that have been triggered for refresh
- There is a simple and effective way to specify which panels get updated on postback
- Update panels work almost seamlessly with the other parts of the MS AJAX framework – components, controls, and behaviors
I have now accepted the fact that Microsoft has put more thought, time and money into making update panels better than any homegrown solution.
In the primer Asp.Net AJAX IN ACTION, the authors take great care to explain precisely how update panels work. They describe how the ajax framework creates and disposes components when an update panel’s contents are updated. For every element in the update panel being disposed, the ajax framework checks to see if there are any controls or behaviors bound to the element that needs to be disposed.
Note: any components not bound to elements are not disposed when update panels are refreshed. When the page_unload() event calls the dispose method of the components an error may occur because the DOM state has changed and the components don’t reflect it.
Why bother with object disposal? Why not just replace the innerHTML of the update panel’s div? Julien Lecompte (from Yahoo!) explains a few issues here. Basically, expando attributes on elements cause memory leaks when not disposed properly. Douglas Crockford (also from Yahoo!) describes the basic workaround. Fortunately for us, however, the Microsoft framework does this already in part through the component dispose method.
When the update panel is about to be loaded, the framework doesn’t know which objects need to be disposed. It has to iterate over all the elements about to be destroyed and check for objects to dispose. The following code is from MicrosoftAjaxWebForms.debug.js (comments in code are mine):
function Sys$WebForms$PageRequestManager$_updatePanel(updatePanelElement, rendering) {
//some code not germane to this post has been omitted here
//this is where the existing content is disposed
this._destroyTree(updatePanelElement);
//and the content is updated
updatePanelElement.innerHTML = rendering;
}
function Sys$WebForms$PageRequestManager$_destroyTree(element) {
if (element.nodeType === 1) {
var childNodes = element.childNodes;
for (var i = childNodes.length - 1; i >= 0; i--) {
var node = childNodes[i];
if (node.nodeType === 1) {
//if the node has a dispose method, call it
if (node.dispose && typeof(node.dispose) === "function") {
node.dispose();
}
//if the node has a sys.ui.control associated with it, call its dispose method
else if (node.control && typeof(node.control.dispose) === "function") {
node.control.dispose();
}
//check for sys.ui.behaviors associated with the node and call dispose on each one
var behaviors = Sys.UI.Behavior.getBehaviors(node);
for (var j = behaviors.length - 1; j >= 0; j--) {
behaviors[j].dispose();
}
//recurse the contents of this node to do it again
this._destroyTree(node);
}
}
}
}
If you know there are no objects bound to the content of the update panel, Asp.Net AJAX In Action suggests that you can avoid the performance penalty imposed by the framework’s memory leak prevention. They suggest removing the update panel div from the DOM after the asynchronous result is returned and before the framework applies the result to the DOM. We can use the pageLoading event to do this.
''' ''' Exposes methods to increase update panel performance when the panel contains content without sys.ui.controls associated with it ''' ''' Public NotInheritable Class UpdatePanelRemoveNodeOnPostback ''' ''' returns string for script to remove the specified control on the client DOM before the page is loaded after an asyncronous postback ''' this saves the atlas framework from having to traverse the existing dom elements and remove all behaviors, thereby increasing performance ''' NOTE: THIS CAN ONLY BE USED WHEN THE CONTROL HAS NO CLIENT SIDE CONTROLS ASSOCIATED WITH IT, specifically, as is the case with some of our ''' gridviews ''' ''' ''' ''' Public Shared Function CreateScriptToRemoveExistingNodeFromUpdatePanelBeforePageLoad(ByVal control As Control) As String Return String.Format(System.Globalization.CultureInfo.InvariantCulture, Resources.UpdatePanelRemoveNodeOnPageLoading.Script, control.ClientID, control.ClientID, control.ClientID) End Function End Class
The function references a script included as a resource:
<script type="text/javascript">
var pageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
pageRequestManager.add_pageLoading(onPageLoading{0});
function onPageLoading{1}(sender, e) {
var itemToRemove = $('{2}');
if (itemToRemove != null) {
itemToRemove.remove();
}
}
</script>
One common place I use this is when an update panel contains a gridview. This is an ideal candidate because the amount of html being disposed can be quite large. In the gridview’s container’s preRender event:
Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs) MyBase.OnPreRender(e) If GridView.Visible Then Dim script As String = UpdatePanelRemoveNodeOnPostback.CreateScriptToRemoveExistingNodeFromUpdatePanelBeforePageLoad(GridView) Me.Page.ClientScript.RegisterStartupScript(Me.GetType, "RemoveGridViewPrePostBackLoad", script) End If End Sub
When the update panel is loaded, the javascript code will fire removing the gridview element from the DOM before the disposal code is fired.
8 Comments »
Andrés on 25 Aug 2008 at 11:51 am #
Hello. I don´t understand this line:
“Return String.Format(System.Globalization.CultureInfo.InvariantCulture, Resources.UpdatePanelRemoveNodeOnPageLoading.Script, control.ClientID, control.ClientID, control.ClientID)”
there’s no Resources.UpdatePanelRemoveNodeOnPageLoading.Script, did you coded a class named UpdatePanelRemoveNodeOnPageLoading inside “Resources” with an string attribute representing the script for element disposal?
thanks.
phil on 25 Aug 2008 at 4:02 pm #
I’m sorry about that. Yes- the code snippet refers to a resource named “UpdatePanelRemoveNodeOnPagLoading” and a data element within the resource called “Script”. The value of the resource is the html escaped version of the javascript code above.
Chris on 05 Feb 2009 at 2:03 pm #
hey, im creating google (j.k.).
this article is very interesting to me. As of right now, im developing a large .NET application that heavily uses AJAX, and im sure down the road we will be using it more.
I found this site googling ‘remove updatepanel from DOM’ and im wondering if i can pick your brain.
I have a page in the app that consists of a bunch of ‘panels’ that are all similar, these panels contain things like Search Results, where each panel is a new result, and as you search it add new panels. Now, these panels are basically an update panel (each one has its own) that has a div inside (the actual panel).
Right now im trying to figure out a better way to remove these panels (each panel has a close button). my old code would post back, and the btn_OnClick event would set the panel as hidden. This works but its quirky (and it posts back).
My new solution is to call a web service on click of the button, and the service would do anything client side that needs to be done (delete rows form a table). Then, on complete, i would like to delete the update panel from the DOM (this would then delete the DIV and everything inside would be stripped from the DOM). Im just wondering about what references are left after this deletetion? The update panel is hooked to events, do i need to delete those?
Basically what im asking you is: How do i delete an update panel from the DOM?
Thanks for your time, and hope to hear back.
-Chris L
andrew on 09 Feb 2009 at 5:59 pm #
First I’ll speak to the the references question, then i’ll tell you what i think you should do instead. When the update panel performs a “postback” (microsoft calls this a partial-postback) the client doesn’t know what will happen to the DOM controls inside the update panel. If you look at the inner workings you’ll notice that the update panel returns all mark-up found inside the refreshed update panels, this includes any new javascript blocks, and any changes to the ENTIRE viewstate, that’s right, the entire viewstate is always passed back and forth during a partial-postback. This is what is so “expensive” when you use an update panel and usually what makes dynamic control creation a nightmare. So, during a partial-postback the client-side microsoft ajax framework parses the entire DOM tree to release any references in hopes to prevent JS memory leaks. Depending on when and how you’re managing you dynamic update panel creation things might just work. If everything works correctly in your eyes as a web user this maybe a viable solution, but i’d recommend you use a tool like sIEve to monitor any memory leaks.
http://home.wanadoo.nl/jsrosman/
But it sounds to me like you’re ready to move beyond the update panel. I say this for multiple reasons but i’ll tell you a couple now. Using a webservice and rendering the results via javascript is going to give you a pretty large performance increase just because you’ll be transmitting less data each round trip. Secondly, everytime i’ve worked with dynamic control creation using the update panel it has not gone well. You need to decide how much work you want to put into this solution, but if you’re looking for something that’s going to scale well and be much more responsive then an update panel, please read on. You should consider constructing a really light weight DTO to push search results back from a webservice and render those results from javascript once the call has completed. It’s really hard for me to guess what your setup is like but here is a little something to get you started.
1) create a really simple class that has all the properties you’ll be passing back from the search, also create a javascript version.
2) Create a simple webservice that you pass search terms it builds and serializes server-side search results and passes them as webservice results in JSON format.
3) Use the ajax control toolkit $common.createElementFromTemplate() method to build your results into fancy mark-up.
http://aspnetresources.com/blog/build_dom_with_toolkit_common_library.aspx
4) You’ll need to hook-up the delete panel event to the newely constructed search results panel, but when that fires you should just be able to remove the whole panel from the DOM and be done with it.
Another Alternative:
ASP.NET AJAX Client Templates
http://quickstarts.asp.net/previews/ajax/
Although, not CTP there may be a much MUCH easier way for you to do this using the asp.net ajax futures preview.
http://www.microsoft.com/downloads/details.aspx?FamilyId=D7101C1A-5993-4F70-9944-4B98F312502D&displaylang=en
Mauro on 04 Jun 2009 at 1:59 am #
I get the error “Input string was not in a correct format” on the line “Return String.Format(System.Globalization.CultureInfo.InvariantCulture, Resources.UpdatePanelRemoveNodeOnPageLoading.Script, control.ClientID, control.ClientID, control.ClientID)”
Do you have an idea of why this is happening?
My Script is a string that contains the javascript code posted above.
Thanks for your help
andrew on 04 Jun 2009 at 12:21 pm #
Yeah, the code is there, it’s the 3rd code chunk.
1.
you have to write that into your resource file… got it?
Mauro on 04 Jun 2009 at 7:16 pm #
Thanks for your prompt response Andrew. I appreciate it. I got rid of the error and started testing my project.
However, the speed doesn’t really seem to improve much. The speed is affected proportionally to the GridView’s number of rows (with a lot of rows, memory usage increases a lot every time).
My GridView is inside an UpdatePanel.
I tried to put the following code in my GridView_OnPreRender and also in the UpdatePanel_OnPreRender but memory usage is still increasing a lot.
# If GridView.Visible Then
# Dim script As String = UpdatePanelRemoveNodeOnPostback.CreateScriptToRemoveExistingNodeFromUpdatePanelBeforePageLoad(GridView)
# Me.Page.ClientScript.RegisterStartupScript(Me.GetType, “RemoveGridViewPrePostBackLoad”, script)
# End If
Do you think I should try to put that bit of code somewhere else in my form? I also tried to create an override PreRender function but still no luck.
I can’t believe how much memory my application uses; it’s ridiculous, if I open my form and perform same changes/transactions on the gridview, in a matter of minutes the memory used by Internet Explorer is over 500MB; it pretty much freezes the client machine and must close and restart IE.
Using Firefox the memory usage still increases, but not even close to what happens with IE. Using Firefox it’s not too bad, but unfortunately I have other “tools” in my page that don’t work on firefox, only on IE.
This is a really critical task for me, I really appreciate your help.
Thanks in advance!
MAURO
phil on 04 Jun 2009 at 7:42 pm #
sounds like you have a memory leak. IE is more prone to these issues than Firefox because of how it separates the DOM in memory from the javascript objects. When elements are destroyed that are referenced by javascript, IE cannot dispose of the memory.
Douglas Crockford has a much better explanation: http://javascript.crockford.com/memory/leak.html
from his site:
Microsoft’s Internet Explorer contains a number of leaks, the worst of which is an interaction with JScript. When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them. Unfortunately, IE’s DOM is not managed by JScript. It has its own memory manager that does not understand about cycles and so gets very confused. As a result, when cycles occur, memory reclamation does not occur. The memory that is not reclaimed is said to have leaked. Over time, this can result in memory starvation. In a memory space full of used cells, the browser starves to death.
You can use these tools to find what is causing the memory leak:
http://home.orange.nl/jsrosman/ (Sieve)
http://blogs.msdn.com/gpde/pages/javascript-memory-leak-detector.aspx (unsupported ms tool)
I’ve used Sieve and it’s worked very well.
Update panels exacerbate this issue because there are an increased number of postbacks causing an increased rate of memory leaks. The greater amount of elements in the leaking update panel, the greater chance you’re leaking more memory.
potentially, adding javascript event handlers via markup or expando attributes will cause this problem e.g. onblur = ‘if(!function()){this.focus();}else{return true;}’ or Page.ClientScript.RegisterExpandoAttribute(ClientID, “evaluationfunction”,”cb_verify”);
Try commenting out columns and/or custom javascript behavior (onclick, onblur, etc.) to see if you can cut down on the memory leaked. The tools listed are a little rough to get your arms around but are very effective when you get them up to speed.
Good luck,
Phil