Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I disable or remove unnecessary form elements before an ASP.NET asynchronous postback?

I'm building a shopping cart page that could potentially contain dozens of separate items. Each item has a collapsible panel that contains several form elements that can be used to customize it. Most of the cart is wrapped in an UpdatePanel so that I can avoid a full postback when the user makes changes. When there are many items in the cart, there are of course many many postback elements, all of which are included in the raw form post, even though each post is really only triggered by the change of a single element (ChildrenAsTriggers=True).

I find that the uncompressed size of the redundant form name/value pairs is 25K. The size is probably far smaller in practice due to gzip compression in the browser (I assume browsers routinely compress the postback values, but haven't gone to the trouble to verify this -- anyone know for sure?) I know I'm being a little anal here ("profile before you optimize!") but I would really like to find a way to eliminate this redundant data. I came up with the following JavaScript:

Sys.WebForms.PageRequestManager.getInstance().add_beginRequest
(
  function(sender, args) {

    var elPostBackTrigger = args.get_postBackElement();

      // skip the first input element, which is expected to be the ScriptManager's hidden field
      removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("input"), elPostBackTrigger, 1);
      removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("select"), elPostBackTrigger);
      removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("textarea"), elPostBackTrigger);
   }
);

function removeNonEssentialPostBackValues(aElements, elPostBackTrigger, iFirstElement) {

    if (iFirstElement == undefined)
        iFirstElement = 0;

    for (var i = iFirstElement; i < aElements.length; ++i) {
        if
        (
            aElements[i] != elPostBackTrigger
            && aElements[i].name
            && aElements[i].name != ''
            && aElements[i].name.indexOf('_') != 0
        ) {
            aElements[i].removeAttribute('name');
            aElements[i].disabled = true;
        }
    }
}

The idea is that if you remove the "name" attribute of a form element, it should no longer be "successful" as per the HTML spec, and ASP.NET should omit it from the postback. Of course you don't want to mess with __VIEWSTATE, __EVENTTARGET, or really anything that begins with an underscore. And you don't want to remove the postback trigger itself.

Unfortunately, this has no effect on the postback values captured through Firebug. It seems that by the time the PageRequestManager fires the beginRequest event, it has already generated the postback name/value pairs. I find this strange, since I would imagine that the main reason for the beginRequest event is to make small changes to the form elements before postback. Maybe PageRequestManager doesn't actually generate the name/value pairs, but rather just pre-generates a list of which elements will be included? Debugging into the MS JavaScript libraries is hard slogging, and I was wondering if anyone could enlighten me here.

EDIT 1: I also tried disabling the form elements in addition to removing their name attributes, but it didn't help. I verified through Firebug that the elements are disabled and names removed before the beginRequest event completes, but somehow ASP.NET still adds them all to the postback.

Jordan Rieger

like image 733
Jordan Rieger Avatar asked Oct 21 '10 01:10

Jordan Rieger


People also ask

How do I stop the current asynchronous postback call?

Click the Refresh button again, and when the message appears, click the Cancel link to cancel the postback. This time, the time displayed in the UpdatePanel control does not change, because the asynchronous postback was canceled.

How do I disable postback?

The postback on submit button can be avoided by giving return=false in the event handler function as below.

Is it possible to cancel and asynchronous postback in Ajax?

You can also cancel the current request by using the cancel property of the Sys. CancelEventArgs class. Note: You can get the ID of the element which has initiated a new postback by 'postBackElement' property of the 'Sys. WebForms.

What is async postback?

An asynchronous postback behaves much like a synchronous postback. All the server page life-cycle events occur, and view state and form data are preserved. However, in the rendering phase, only the contents of the UpdatePanel control are sent to the browser. The rest of the page remains unchanged.


1 Answers

I finally got around to stepping through MicrosoftAjaxWebForms.debug.js and verified that the PageRequestManager does indeed generate the post collection prior to raising the beginRequest event. Worse, the collection is stored in a StringBuilder in a local variable, so I couldn't manipulate it, even if I was willing to access private members.

The solution I found was to call ScriptManager.RegisterOnSubmitStatement from Page_Load in code behind, to setup a JavaScript beforePostback() function. I verified that this function does indeed get called before the PageRequestManager begins its dirty work, so I was able to put my calls to removeNonEssentialPostBackValues() in there.

    If Not ScriptManager.GetCurrent(Me).IsInAsyncPostBack Then
        ScriptManager.RegisterOnSubmitStatement(Me, Me.GetType(), "beforePostback", "beforePostback()")
    End If

And the JavaScript:

function beforePostback() {
    var nlTriggers = document.getElementsByName(document.aspnetForm.__EVENTTARGET.value);
    if (nlTriggers && nlTriggers.length > 0) {
        var elPostBackTrigger = nlTriggers[0];
    }
    
    // skip the first input element, which is expected to be the ScriptManager's hidden field
    removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("input"), elPostBackTrigger, 1);
    removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("select"), elPostBackTrigger);
    removeNonEssentialPostBackValues(document.aspnetForm.getElementsByTagName("textarea"), elPostBackTrigger);
}

With this code, I found that I could reduce the size of the postback request in my hand-crafted nightmare scenario from about 34K down to about 15K. I also verified that the important form values were still getting passed through to the page, and that the code-behind could still discern which element triggered the postback.

Obviously, this technique is quite heavy-handed, and would not fit many scenarios. For example, in a typical web form scenario where the user must edit multiple fields and pass validation, you wouldn't want to do this, because you would need all the form values to be posted. Really, this technique is a simple way of getting closer to a client-side interface where the JavaScript makes only the minimum server calls necessary to carry out its commands. I've built such applications before and while their performance is very good, they are tedious to develop and maintain. For this project I wanted to leverage ASP.NET as much as possible while maintaining high performance.

UPDATE: I just tested this in IE 8 (I had been using Firefox 3.6) and it is a very slow process to disable the hundreds/thousands of form elements. It takes 1 or 2 seconds, unlike Firefox 3.6, which doesn't even blink. I shudder to think how slow IE 7 might be. I guess I will have to abandon this technique for now, since the delay introduced for the large amount of IE users would be much greater than the delay caused by the extra 20K of postback data for the unnecessary form elements.

UPDATE 2: If you are still maintaining complex ASP.NET WebForms pages 10 years later (my sympathies) you will probably find that modern browsers and devices have no problem blasting through the JS in my helper function almost instantly.

like image 108
Jordan Rieger Avatar answered Sep 24 '22 17:09

Jordan Rieger