An Unusual UpdatePanel

The code you are about to see was mostly to prove a point, to myself, and probably has limited applicability. Nonetheless, in the remote possibility this is useful to someone here it goes…

So this is a control that acts like a normal UpdatePanel where all child controls are registered as postback triggers except for a single control specified by the TriggerControlID property. You could basically achieve the same thing by registering all controls as postback triggers in the regular UpdatePanel. However with this, that process is performed automatically.

Finally, here is the code:

public sealed class SingleAsyncTriggerUpdatePanel : WebControl, INamingContainer
{
    public string TriggerControlID { get; set; }

    [TemplateInstance(TemplateInstance.Single)]
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public ITemplate ContentTemplate { get; set; }

    public override ControlCollection Controls
    {
        get
        {
            this.EnsureChildControls();

            return base.Controls;
        }
    }

    protected override void CreateChildControls()
    {
        if (string.IsNullOrWhiteSpace(this.TriggerControlID))
            throw new InvalidOperationException(
                "The TriggerControlId property must be set.");

        this.Controls.Clear();

        var updatePanel = new UpdatePanel()
        {
            ID = string.Concat(this.ID, "InnerUpdatePanel"),
            ChildrenAsTriggers = false,
            UpdateMode = UpdatePanelUpdateMode.Conditional,
            ContentTemplate = this.ContentTemplate
        };

        updatePanel.Triggers.Add(new SingleControlAsyncUpdatePanelTrigger
        {
            ControlID = this.TriggerControlID
        });

        this.Controls.Add(updatePanel);
    }
}

internal sealed class SingleControlAsyncUpdatePanelTrigger : UpdatePanelControlTrigger
{
    private Control target;

    private ScriptManager scriptManager;

    public Control Target
        {
            get
            {
                if (this.target == null)
                {
                    this.target = this.FindTargetControl(true);
                }

                return this.target;
            }
        }

    public ScriptManager ScriptManager
        {
            get
            {
                if (this.scriptManager == null)
                {
                    var page = base.Owner.Page;

                    if (page != null)
                    {
                        this.scriptManager = ScriptManager.GetCurrent(page);
                    }
                }

                return this.scriptManager;
            }
        }

    protected override bool HasTriggered()
    {
        string asyncPostBackSourceElementID = this.ScriptManager.AsyncPostBackSourceElementID;

        if (asyncPostBackSourceElementID == this.Target.UniqueID)
            return true;

        return asyncPostBackSourceElementID.StartsWith(
            string.Concat(this.target.UniqueID, "$"),
            StringComparison.Ordinal);
    }

    protected override void Initialize()
    {
        base.Initialize();

        foreach (Control control in FlattenControlHierarchy(this.Owner.Controls))
        {
            if (control == this.Target)
                continue;

            bool isApplicableControl = false;
            isApplicableControl |= control is INamingContainer;
            isApplicableControl |= control is IPostBackDataHandler;
            isApplicableControl |= control is IPostBackEventHandler;

            if (isApplicableControl)
            {
                this.ScriptManager.RegisterPostBackControl(control);
            }
        }
    }

    private static IEnumerable<Control> FlattenControlHierarchy(
        ControlCollection collection)
    {
        foreach (Control control in collection)
        {
            yield return control;

            if (control.Controls.Count > 0)
            {
                foreach (Control child in FlattenControlHierarchy(control.Controls))
                {
                    yield return child;
                }
            }
        }
    }
}

You can use it like this, meaning that only the B2 button will trigger an async postback:

<cc:SingleAsyncTriggerUpdatePanel ID="Test" runat="server" TriggerControlID="B2">
    <ContentTemplate>
        <asp:Button ID="B1" Text="B1" runat="server" OnClick="Button_Click" />
        <asp:Button ID="B2" Text="B2" runat="server" OnClick="Button_Click" />
        <asp:Button ID="B3" Text="B3" runat="server" OnClick="Button_Click" />
        <asp:Label ID="LInner" Text="LInner" runat="server" />
    </ContentTemplate>
</cc:SingleAsyncTriggerUpdatePanel>
Advertisements