2012/03/11

Script registration labyrinth in ASP.NET WebForms


I'm always lost when trying to register a javascript fragment that must be executed on the client side after the postback. There are several usable methods in ASP.NET WebForms and I never know which one to choose.

Page.ClientScript
Page.ClientScript.RegisterStartupScript – registers the startup script. What does it mean “startup”? It is something that is executed when the browser finishes page loading, which is guaranteed by the fact that “startup” scripts are rendered just before closing <form> tag. Almost all elements on the page are processed by the browser at this point and can be referenced from that “startup” script.
Page.ClientScript.RegisterClientScriptBlock – registers the script block, so it is rendered somewhere near after the <form> element opening. This block is executed before the DOM is fully processed and before any “startup” scripts are executed. “ClientScript” blocks are good place for javascript functions.

Page
Page.RegisterStartupScript and Page.RegisterClientScriptBlock are marked as obsolete and they do nothing but Page.ClientScript call. Just ignore them.

ScriptManager
Now what about ScriptManager class? RegisterClientScriptBlock and RegisterStartupScript are there as well. What is the justification for such duplication? The only difference, according to the documentation (here and here), is that the scripts are “included every time that an asynchronous postback occurs”. This formulation is slightly confusing. Scripts registered by ScriptManager’s methods are rendered by non-asynchronous postback as well. ClientScript’s methods are never rendered by asynchronous-postbacks.

Script registration after asynchronous postback
The javascript block is not executed if it is a part of markup in AJAX response. Browser simply doesn’t process such block. Consider such silly ASPX:
<asp:Button runat="server" ID="Button1" Text="Do postback" OnClick="Button1_Click" />
<asp:Literal runat="server" ID="Literal1" />

and C# code behind with a script:
protected void Button1_Click(object sender, EventArgs e)
{
    Literal1.Text = "<script>alert('Button1 clicked')</script>";
}

Button1 causes postback (when clicked) and event handler fills Literal1 with the javascript block. This javascript block is execeuted and the alert message is shown when browser loads the (synchronous) postback response.

Let’s move to asynchronous postback:
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
    <ContentTemplate>
        <asp:Button runat="server" ID="Button1" Text="Do AJAX call" OnClick="Button1_Click" />
        <asp:Literal runat="server" ID="Literal1" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Button1" />
    </Triggers>
</asp:UpdatePanel>

Code behind remains unchanged.

No message is shown after the Button1 is clicked. Body of the response should look something like this:
139|updatePanel|UpdatePanel1|
        <input type="submit" name="Button1" value="Do AJAX call" id="Button1" />
        <script>alert('Button1 clicked')</script>
|152|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NQ9kFgICAw9kFgICAw9kFgJmD2QWAgI
DDxYCHgRUZXh0BSk8c2NyaXB0PmFsZXJ0KCdCdXR0b24xIGNsaWNrZWQnKTwvc2NyaXB0PmRk88Fv/gu
MXM8Xq/lT5WC+dG66354=|48|hiddenField|__EVENTVALIDATION|/wEWAgL/oouVBgKM54rGBmox+
1LEg7FJSnLOTrjRf5eL73gK|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|
||13|updatePanelIDs||tUpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs|
|UpdatePanel1|2|asyncPostBackTimeout||90|13|formAction||WebForm8.aspx|

Alert script is rendered into markup but it is not executed by the browser. How is it possible that script registration via ScriptManager works?

This code behind modification could discover the secret:
protected void Button1_Click(object sender, EventArgs e)
{
    ScriptManager.RegisterStartupScript(this, this.GetType(), "script", "<script>alert('Button1 clicked')</script>", false);
}

The alert message box is shown this time. Here is the response:
98|updatePanel|UpdatePanel1|
        <input id="Button1" name="Button1" type="submit" value="Do AJAX call" />
    |52|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NWRkwPvseEH2ojph8EJh0MFS5L+TksA
=|48|hiddenField|__EVENTVALIDATION|/wEWAgKaoIz3DwKM54rGBr7RMEQMtuoxxWPUsRRwWxmrCH
/Z|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|||13|updatePanelIDs||t
UpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs||UpdatePanel1|2|asyncP
ostBackTimeout||90|13|formAction||WebForm8.aspx|45|scriptStartupBlock|
ScriptContentWithTags|{"text":"alert(\u0027Button1 clicked\u0027)"}|

There are two significant changes in the response: the script is not rendered (unsurprisingly) in the Literal control and there is something more in the response: scriptStartupBlock.  ASP.NET AJAX library ensures that script from this “section” is executed after receiving the response. ScriptManager.RegisterClientScriptBlock is very similar but it uses scriptBlock “section”.

It is quite easy to navigate through the labyrinth when it is clear how things work under the hood.