Search code examples
c#asp.netwebformspostbackascx

Persist User Control(ASCX) EventHandler Object Across Postbacks


Say that you have a User Control(ASCX):

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Test.ascx.cs" 
    Inherits="WebApplication1.Test" %>
<asp:Button ID="test" runat="server" OnClick="test_Click" Text="test" />

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class Test : System.Web.UI.UserControl
    {        
        public event EventHandler Method1;

        public EventHandler Method2
        {
            get { return (EventHandler)Session[this.ClientID + "|Method2"]; }
            set { Session[this.ClientID + "|Method2"] = value; }
        }

        public EventHandler Method3
        {
            get { return (EventHandler)ViewState["Method3"]; }
            set { ViewState["Method3"] = value; }
        }

        protected void test_Click(object sender, EventArgs e)
        {
            if (Method1 != null)
                Method1(this, e);

            if (Method2 != null)
                Method2(this, e);

            if (Method3 != null)
                Method3(this, e);
        }
    }
}

And you have a page that you place that UserControl on:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" 
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
    Inherits="WebApplication1._Default" %>
<%@ Register Src="~/Test.ascx" TagName="Test" TagPrefix="uc1" %>

<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
    <uc1:Test ID="TestControl" runat="server" />
    <asp:Label ID="Result" runat="server" Text="Test" />
</asp:Content>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class _Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            //Uncomment each one to observe their behavior

            //Will never trigger(unless reset on each postback)
            //if (!IsPostBack) TestControl.Method1 += new EventHandler(Test);

            //Will trigger(the code will run) but makes no change to the parent page
            //if (!IsPostBack) TestControl.Method2 += new EventHandler(Test);

            /*
            Will fail because it can't serialize the "EventHandler" object
            You can play around by changing this to a delegate type 
            and making that serializable, but that fails as well  
            */
            //if (!IsPostBack) TestControl.Method3 += new EventHandler(Test);
        }
        protected void Test(object sender, EventArgs e)
        {
            Result.Text = Guid.NewGuid().ToString();
        }
    }
}

But for this given User Control we want to define an event to capture a button's click and have that trigger the event of the User Control's parent's choosing. Also due to requirements outside of the scope of this example, say that we would like to only ever set the value of the "MethodX" EventHandler once, ie not recreate the control/property on every Postback(in this example I demonstrate this concept by only setting the event once on the page load).

How can this be accomplished? All three methods that I've attempted fail in there own unique ways... I've so far not found a similar question related to the persistence of events, most posts deal with variable/object persistence("Use the ViewState" or various other ways of persisting non-event objects, see link) or simply direct the user to "recreate the control on every Postback(including related properties)", see link.

Any feedback or insight would be helpful, especially confusing to me is the behavior of "Method2", I think I don't have a full enough understanding of the page life-cycle to understand why that's occurring.


Solution

  • You need to register events before load event (try on init). Click event triggering happens between init and load events.

    UPDATE: Sorry, you're right click event is raised between Load and LoadComplete.

    UPDATE: I think I found what you need and, disappointingly, it cannot be posible what do you want. An instance delegate of a non-static method is strongly dependent of the instance from the method's class. Every request, the instance of the page is created, and when the request's end, it's disposed. Pages aren't singleton instances. This is the reason why, even if you store the handler in the session, it cannot be reused, because it's trying to call a method from a disposed instance.

    This is a good article about how events and delegates works: http://csharpindepth.com/Articles/Chapter2/Events.aspx

    The most important part is this:

    [..] the key points of data in any particular delegate instance are the method the delegate refers to, and a reference to call the method on (the target). For static methods, no target is required.

    That target is the page/usercontrol instance of the page the first moment you created. When you are doing a Postback, the page/usercontrol instance changed, but your persisted handler's target not.