Living with ASP.NET AJAX: MasterPages, UpdatePanels and cross-content AsyncPostBackTriggers

I’ve moved this blog to https://damianedwards.wordpress.com. I won’t be updating this space any longer.
Please update your RSS subscriptions to https://damianedwards.wordpress.com/feed.

One of the great things about the ASP.NET AJAX UpdatePanel control is the ability to trigger a refresh of the UpdatePanel’s contents based on the event of another control on the same page.  This means you can have multiple UpdatePanels hosted in your page and only have them update themselves when necessary based on the events of the other controls on the page (you will need to explicitly set the UpdateMode property of the UpdatePanels to Conditional to achieve this). The markup to achieve this is very simple:

    <asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button ID="btnRefresh" runat="server" Text="Refresh" />
        </ContentTemplate>
    </asp:UpdatePanel>
    
    <asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvListOfThings" runat="server"></asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="btnRefresh" EventName="Click" />
        </Triggers>
    </asp:UpdatePanel>

Here we have two UpdatePanels defined with the second one including a declaration for an AsyncPostBackTrigger that wires up the click event of the button contained in the other UpdatePanel. When the user clicks the button, the second UpdatePanel will refresh its contents. Easy.

Issues arise however when you start to use MasterPages with multiple ContentPlaceHolders defined, a fairly common scenario when designing ASP.NET UI beyond the basic tutorials. For some reason (I admit I don’t know why) you cannot specify a control in a different Content pane as the source of an AsyncPostBackTrigger. So this:

<asp:Content ID="conMain" runat="server" ContentPlaceHolderID="cphMain">
    <asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button ID="btnRefresh" runat="server" Text="Refresh" />
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>

<asp:Content ID="conRight" runat="server" ContentPlaceHolderID="cphRight">
    <asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvListOfThings" runat="server"></asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="btnRefresh" EventName="Click" />
        </Triggers>
    </asp:UpdatePanel>
</asp:Content>

Results in this:

Server Error in ‘/AjaxSample’ Application.


A control with ID ‘btnRefresh’ could not be found for the trigger in UpdatePanel ‘updDynamicContent’.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: A control with ID ‘btnRefresh’ could not be found for the trigger in UpdatePanel ‘updSummary’.

Not much fun! So how do we go ahead and make this possible?

A Solution

To make this possible and stay with the declarative model of defining AnsycPostBackTriggers as much as possible we can create a small custom server control to proxy the event across the Content panes of our content page. We can drop this control into the same content pane as the UpdatePanel we wish to define the trigger on, set the trigger to wire up to the control’s EventProxied event, and then in the handler for the actual control’s event we wish to update on we call the proxy control’s ProxyEvent method.

First of all, add a new class file to your website called EventProxy.cs and paste in the following code:

public class EventProxy : Control, IPostBackEventHandler
{
    public EventProxy()
    { }

    public void RaisePostBackEvent(string eventArgument)
    { }

    public event EventHandler<EventArgs> EventProxied;
    protected virtual void OnEventProxy(EventArgs e)
    {
        if (this.EventProxied != null)
        {
            this.EventProxied(this, e);
        }
    }

    public void ProxyEvent(EventArgs e)
    {
        OnEventProxy(e);
    }
}

This very basic control defines a public event EventProxied and a public method ProxyEvent(EventArgs) which just raises the EventProxied event. The control must implement the IPostBackEventHanlder interface in order for it to be allowed to be delcared as the source of an AsyncPostBackTrigger.

Now in your content page add a @Register directive to register the application’s dll as a source of controls with the following line:

<%@ Register Assembly="AJAXSample" Namespace="AJAXSample.Controls" TagPrefix="as" %>

(Be sure to change the assembly name and namespace to match those of your application)

Now we can change our markup sample from before to use the new control like so:

<%@ Register Assembly="AJAXSample" Namespace="AJAXSample.Controls" TagPrefix="as" %>
<asp:Content ID="conMain" runat="server" ContentPlaceHolderID="cphMain">
    <asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button ID="btnRefresh" runat="server" Text="Refresh" OnClick="btnRefresh_Click" />
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>

<asp:Content ID="conRight" runat="server" ContentPlaceHolderID="cphRight">
    <asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvListOfThings" runat="server"></asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="epRefreshButtonClicked" EventName="EventProxied" />
        </Triggers>
    </asp:UpdatePanel>
    <as:EventProxy ID="epRefreshButtonClicked" runat="server" />
</asp:Content>

Change your page’s code-behind to call the EventProxy control’s ProxyEvent method in the handler for the button’s click event like so:

protected void btnRefresh_Click(object sender, EventArgs e)
{
    epRefreshButtonClicked.ProxyEvent(e);
}

And that’s it! We now have a reusable control that allows us to declaratively use the event of a control in a different content pane as the trigger for a partial postback of our UpdatePanel. You’ll need to add an instance of the EventProxy control to your content pane for each event of a control in a different content pane you wish to use as an AsyncPostBackTrigger. The only code we have to write each time is to call the appropriate EventProxy control’s ProxyEvent method from the event handler for the event we wish to use as the trigger.

I hope this helps someone.


2 Comments on “Living with ASP.NET AJAX: MasterPages, UpdatePanels and cross-content AsyncPostBackTriggers”

  1. Pat says:

    You, good sir, are awesome. I used this bit of code, converted to VB, to refresh a shopping cart in an update panel in a content placeholder from a button in a different placeholder in a repeater! Thank you very much! I am a database developer and small business owner that used to do some asp.net almost 8 years ago – I’m building a site for my friends and I and it has been a complete P I T A, to say the least. Thanks again!!!

  2. Pat says:

    Only issues I need to resolve now is that the whole page is posting back – I thiought the point of ajax was to avoid this. Is there a way to easily only have the update panel refresh/postback?

    Don’t know how clear I was, but…
    I have a repeater (content pane) that contains a button per row and an update panel (right pane) that needs to refresh when any button is pushed. Seems as thoug the whole page is refreshing – not just the update pane.

    Thanks again.


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