AJAX Accordion OutOfMemoryException

Posted: 29th March 2010 in AJAX, JQuery, WCF

Recently I came across a problem with the ASP.NET AJAX accordion control which caused my machine to have a System.OutOfMemoryException. This was caused by an attempt to bind a large result set returned from a WCF service  to the accordion control. The error never directly specified out of memory, but instead display the following webpage error: “Could not get the value property. Not enough storage is available to complete this operation”

image

This is actually a SOAP error, but its effectively the same thing. There is simply too much data for the machine to cope.

After some investigation I found that this problem was down to the fact that the accordion control not only loads the header detail for the bound list, but all the content detail too. In my case, the content control contained a lot of dropdown lists.  Firstly, I thought about implementing paging, but after consultation with the client I soon discovered that this was not an option.

So, to allow me to load the full list I had to change how the master and detail worked. Here’s how I went about it.

Firstly, I created a user control for the detail to be displayed and instead of loading the control in the html of the accordion content template I added a place holder as shown in Figure 1.

Figure 1
  1. <ContentTemplate>
  2. <asp:PlaceHolde

Now, I have to load this control dynamically. However, I only want to load it for the first item of the accordion. To do this I used the OnItemDataBound event of the accordion as can be seen below in Figure 2.

Figure 2
  1. protected void CircuitAccordion_ItemDataBound(object sender, AccordionItemEventArgs e)
  2. {
  3. if (e.ItemType != AccordionItemType.Content || ((Accordion) sender).Panes.Count > 1)
  4. {
  5. return;
  6. }
  7. var circuitDto = e.Item as CircuitDto;
  8. if (circuitDto == null)
  9. {
  10. return;
  11. }
  12. var circuitDetail = (CircuitDetail)LoadControl(“UserControls/CircuitDetail.ascx”);
  13. circuitDetail.Circuit = circuitDto;
  14. var circuitUserControlPlaceHolder =
  15. e.AccordionItem.FindControl(“circuitUserControlPlaceHolder”) as PlaceHolder;
  16. if (circuitUserControlPlaceHolder == null)
  17. {
  18. return;
  19. }
  20. circuitUserControlPlaceHolder.Controls.Add(circuitDetail);
  21. }

Great, now the first item loads, but not the rest.  To get the other items to load I needed to use a little jquery and a AJAX enabled WCF service (to get the data for each item).

To override the functionality of the accordion control I first need to create a client side event handler for  the on selected index changed event. This can be seen in figure 3 and attach the handler on the page load figure 4

Figure 3
  1. // handles the event which fires on the accordion selected index changing
  2. function onAccordionSelectedIndexChanged(sender, eventArgs) {
  3. var contentTemplate = $(‘#’ + sender._id.replace(‘_AccordionExtender’, ‘_Pane_’ + sender.get_SelectedIndex() + ‘_content’));
  4. contentTemplate.append($(“fieldset[id*=’DetailsControl’]:visible”));
  5. var headerTemplate = sender._id.replace(‘_AccordionExtender’, ‘_Pane_’ + sender.get_SelectedIndex() + ‘_header’);
  6. var id = $(‘#’ + headerTemplate + ‘_idHiddenTextBox’).val();
  7. var tabContainer = $get(‘<%=locationTabContainer.ClientID%>’).control;
  8. loadDetailControlData(tabContainer.get_activeTab().get_headerText(), id);
  9. }

In figure 3 I perform the following steps:

  • Line 3 – I get reference to the content template of the header which has been clicked.
  • Line 4 – I append the visible details user control to the associated content template of the selected index of the header template clicked.
  • Line 5 – Get reference to the header template to allow access to the hidden textbox control.
  • Line 6 – Retreive the value of the primary key, which is in a hidden textbox control named idHiddenTextbox in the header template pane.
  • Line 7- Get reference to the selected tab
  • Line 8 –  Pass the id and the selected tab control to the loadDetailControlData function in figure 5

To add the event handler to the control I use the javascript pageLoad() function as can be seen in figure 4.

Figure 4
  1. function pageLoad() {
  2. $find(‘<%= devicesAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  3. $find(‘<%= cabinetsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  4. $find(‘<%= endBCircuitsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  5. $find(‘<%= endACircuitsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  6. $find(‘<%= nonNetworkDevicesAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  7. }

In this particular situation, I have a tab control which, in turn, contains the accordion controls. So, I created a JavaScript function to load the detail control data dependent on the selected tab, seen in figure 5 which call the WCF service shown in figure 6 and ultimately set the field values from the service response seen in figure 7.

figure 5
  1. function loadDetailControlData(tabHeaderText, id) {
  2. switch (tabHeaderText) {
  3. case ‘Cabinets’:
  4. getCabinet(id);
  5. break;
  6. case ‘Devices’:
  7. getDevice(id);
  8. break;
  9. case ‘End A Circuits’:
  10. case ‘End B Circuits’:
  11. getCircuit(id);
  12. break;
  13. case ‘Non-Network Devices’:
  14. getNonNetworkDevice(id);
  15. break;
  16. }
  17. }

Figure 6 shows the call to the WCF service using jquery. This function is called from the function above in Figure 5.

Figure 6
  1. function getCircuit(circuitId) {
  2. $.ajax({
  3. url: http://localhost:10785/LocationServiceAjax.svc/GetCircuit&#8221;,
  4. type: “POST”,
  5. contentType: “application/json”,
  6. data: ‘{“circuitId”:’ + circuitId + ‘}’,
  7. dataType: “json”,
  8. success: function(data) {
  9. setCircuitDetail(data.d);
  10. }
  11. });
  12. }

Seen below is the call-back function to set the control values of the details form which are returned from the WCF Ajax response seen in figure 6

Figure 7
  1. // Sets the circuit detail form callback from the ajax wcf call.
  2. function setCircuitDetail(circuit) {
  3. if (circuit == null) { alert(“Something is not configure correctly.”); return; }
  4. $(“input[id$=’circuitReferenceTextBox’]:visible”).val(circuit.CircuitReference);
  5. $(“select[id$=’circuitUsageDropDownList’]:visible”).val(circuit.Usage.Description);
  6. $(“select[id$=’circuitTypeDropDownList’]:visible”).val(circuit.Type.Name);
  7. $(“select[id$=’circuitOwnerDropDownList’]:visible”).val(circuit.Owner.Name);
  8. $(“select[id$=’circuitStatusDropDownList’]:visible”).val(circuit.Status.Status);
  9. $(“select[id$=’endACircuitLocationDropDownList’]:visible”).val(circuit.EndALocationCode);
  10. $(“select[id$=’endBCircuitLocationDropDownList’]:visible”).val(circuit.EndBLocationCode);
  11. }

Hopefully this post helps someone as it took me a while to work it out… 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s