Fun with callbacks Part 4: what about postback events?

One thing that's certainly not that clear enough about callbacks is what happens with regular postback events and control state. During a callback, we reconstruct the state of the page as it was during the last postback. The reason we are doing this is that we want the logic in the callback method to be able to access the rest of the page with controls in the right state.

We don't update the ViewState during a callback because we allow for parallel callbacks (by the way, don't let the "useAsync" parameter of GetCallbackEventReference fool you: it's really about parallel or sequential). If we had both parallel callbacks and ViewState updating, the page would easily get to an inconsistent state. What control developers do to work around this problem is to store the client-side and callback state changes for their control in a separate hidden field and replay these changes server-side on the next postback. This adds a little complexity to controls such as the TreeView.

But how are we maintaining the last postback state during callbacks? One of the first things the WebForms client script does in a callback page is to initialize an object and store the contents of the form in it, before it can be modified. This object will be sent back to the server with the callback parameters. It means that your network payload is more important than just your parameters but that's the price of statefulness (more on this later). When we receive the callback server-side, this enables us to reconstruct the state of the page as it was during the last postback (which is the last consistent state we know). We don't want the actual state of the form at the time the callback was made because this state might be inconsistent (because of validated fields and so on). If you want a perfectly up-to-date version of a form field (and not just state data), it means that it is really a parameter of your callback and should be transmitted as such.

I know this needs a little getting used to (I needed it myself), but it also raises a question: if the state is reconstructed exactly like it was during the last postback, and if an event (for example a click event or a textbox change event) happened during the last postback, will this event be re-triggered on each callback?

The answer is no. To trigger a click event, depending on the button being a regular button, an image button or a link button, we look at the form field for this button or at a special hidden field pair. Both of these are stateless, which means that even if their value is non-empty during the server postback, once they get back to the client, they are empty again. Thus, when we send the initial form state during a callback, these fields will be empty and the event will not be triggered again. For a textbox change event, it's a little different. The change event compares the actual value of the form field with the last value it stored in ViewState. During a callback, we get the initial value, which is identical to the value in ViewState because the latter was stored at the end of the last postback, after change events. So no change event, except if you disabled ViewState (but it's the same thing with postbacks). Which is one more reason why we don't want the updated value of the field at this time: we don't want the change event to be triggered during every callback until the next postback.

Previous episodes: Part 1, Part 2, Part 3

21 Comments

  • I have a parent DropDownList (like province)and dependent DropDownList(like city). after callback, i update the city dropdown list. when I do a postback, the second list items are all gone due to the ViewState not refreshed during the callback. How should I deal with this situation?

  • Yale: short answer is to start using Atlas (Microsoft AJAX). An UpdatePanel should make that scenario ultra-easy. Even better, use the chained drop down lists from the Atlas Toolkit which I think are doing exactly what you want to do. Long answer, you need to keep track (in a hidden field usually) of the client-side changes. On the next postback, look at this field and "replay" the changes that happened client-side so that the server-side state can catch up. That's what the TreeView is doing. But really, you should check out Atlas. Hope this helps.

  • Have you tried doing asynchornous calls while you are inside a loop ? It does not work.

    I mean

    while(i< 5)
    {
    send_request_to_server(i)

    }

    Only the last request will successfully make it.

    Question: How do we send a request, then wait till we
    get an answer and then go for a second round ?

  • Moderage: Well, first I'm not sure it's a good idea to do server requests in a loop. You should probably batch those requests together to minimize network roundtrips. But if you must, using a queue is the right way to do this asynchronously.

  • Moderage:I think you can use window.setTimeout and golbal virable for your poupose.

  • I think the right way to do callbacks in a loop is to use a client-side pattern like:
    function InvokeServer()
    {
    //invoke server-side functionality here via
    //Page.ClientScript.GetCallBackEventReference
    }

    function ShowResultFromServer(result, context)
    {
    if(someCondition)
    InvokeServer(); //again
    }
    InvokeServer is the function containing the Page.ClientScript.GetCallBackEventReference script. ShowResultFromServer is the function that is invoked on the client to render any results (remember that its name is passed to GetCallBackEventReference along with another function in case of a server-side excetion). When the browser renders the results of the first callback you eventually do a SECOND callback (based on someCondition), thus performing a recursive client-server-client while-loop thingie. This way you guarantee that the next callback in the loop starts exactly after the previous one, no need to set timeouts.

  • Orlin: as I said in a previous comment, the right pattern is not to do that at all. If you know that you're going to have to requiery as soon as you're done, why don't you batch the calls? This looks like a typical case of Ajax abuse that's going to result in catastrophic performance. If you really, really have to do something like this, at least the timeout approach gives an opportunity to introduce a pause in the whole process and relax the pressure on the server and network. But really, try to find another way.

  • I have had a requirement for this when aspnet process permissions need to be tested one at a time with the user informed of progress (as is quite possible one of them will time out and the user needs to know where it got stuck).

    Anyway you can't do using Orlin's method as this is the way I was trying at first. You get stuck in a recursive loop as it seems the clientCallback method specified in GetCallbackEventReference() must return before the WebForm_DoCallback() method can be successfully called again (ie you can't call it from within the clientside event handler the receives the result).

  • Jai: use setTimeout to queue the next request.

  • Hello Bertrand,
    I need to build a custom wizard in code at the page load from database records of what fields to include for wizard number 43 etc. I have done it by having a base page that has a default wizard on it, that I pass to serverside classes to build the steps and fields in the page initialise event. This work fine but reverts to the base wizard as soon as the page posts back when I click "next"(duh). How do I maintain the custom wizard and any entered data through postbacks? The answer may be beyond me, but I haven't got a clue how to go about it.
    Thanks

  • David: It's hard to debug your code without seeing it but apparently you're not recreating the controls in their current state on each postback, or maybe you're recreating the controls but with the wrong ids. There are many things that can go wrong, but I'd start by reading this:
    http://weblogs.asp.net/infinitiesloop/archive/2006/08/25/TRULY-Understanding-Dynamic-Controls-_2800_Part-1_2900_.aspx

  • Ok, riddle me this if you please. I am building an ASP.NET (VB) site using Master Pages. The Masterpage is simply to have a common header. In the content for the page in question, I have a two column table. The left column has a treeview with a list of objects. Each object has properties...each with potentially a different value. The right column is a dynamically created table that in each row has either a label, dropdownlist, or a checkbox. My postback code rebuilds this table and populates the values appropriate to the object selected in the treeview.

    My problem is that if I click from one object to another on the treeview, data is not updated on the table...even though I show in a logger (listbox) that I'm attempting to set the values.

    I toyed with the loadcomplete routine and changes made to the objects in the right column stick. But the function that "rebuilds" the table during postback does not.

    Your help would be greatly appreciated.

    Jon

  • Jon: it's hard to say from just this description and without seeing the code. Are you handling the selected node changed event from the treeview to change what's in the table?

  • I have a question in the article its said that when we have a client call back the click event of a normal button with postback wont work... how can i fixed that problem i want to have some objects with client call back and some others with postbacks

  • Snt: I don't know where you got this idea. There is no reason why postbacks wouldn't work normally even "when we have a client callback", although I'm not sure what you mean by that.

  • I have a question. we are using client call back in our application. its calling serverside event in mozilla and ie but safari does not call server side event. can you help me how to fix this

  • I'm pretty sure Safari should work. Can you contact me through the contact form and send me a simple repro?

  • hi, In my masterpage I am having a devexpress treelist and each time I click a node it will create a different user control in the content page based on the node I clicked during a callback of my focusednodechanged clientSideEvent of my treelist. When I click the treenode which I have already clicked before, I want my usercontrol to maintain its viewState. What is the best way to do it. I don't want to load all usercontrols on page load. Thanks, Ram.

  • @ram: you should read this post: http://weblogs.asp.net/infinitiesloop/archive/2008/04/23/truly-understanding-dynamic-controls-by-example.aspx and the one before it.

  • Hi,
    I am using 4 dropdownlists. In the pageload 1st ddl is populated, when I select the ddl1 value ddl2 should be populated Again when i select the value from ddl2 ddl3 should be populated based on the values selected in ddl1 and ddl2 in the same manner ddl4 should be filled. I don't want to use AJAX and I want to use IcallBackEventHandler Interface.
    Thank you

  • @Siva: don't. All this is pretty much obsolete. Use a JSON web service and jQuery.

Comments have been disabled for this content.