ASP.NET: Which Control Posted Back?
by Peter A. Bromberg, Ph.D.

Peter Bromberg

" There used to be a real me, but I had it surgically removed. " -- Peter Sellers

"Which control generated my postback?"

This is a very common issue that crops up from time to time. I wrote a piece about this a couple of years ago, but the treatment was kind of narrow. And, since it seems to be coming back again on our messageboards, I thought it would be a good time to do a little investigation and write up something useful.

For starters, we should formulate the basics of what a PostBack event is, and how it is generated. Most ASP.NET Controls are "Server Controls". This means that when an action occurs, the Page, which hosts all its controls inside <FORM... tags, makes a regular old HTTP POST, with itself being the target URL. This is referred to as a "POSTBACK". When this happens, the ASP.NET Page infrastructure is able to see which control was clicked, changed, or whatever the postback generating action was, and is able to react apppropriately when the page is reloaded after passing through the server a second time. if you have an event handler method that is supposed to react to Button1_Click, this code is run, and so on.

But, how does the Page class "know" which control was "clicked, changed, or whatever"? Very simply, each control generates plain old client-side Javascript. Yup, it ain't rocket science, dood!

Since HTTP is stateless and there is no "invisible connection" between the server-side classes and the client-side HTML document in your browser, how can a client-side event be handled on the server side? In ASP.NET, this is done by using hidden form fields. A client-side event, such as an "onclick" or "onchange" event, can be captured by a JavaScript event handler. ASP.Net provides the javascript __doPostBack() function. This records the name of the object that fired the event, as well as any additional event information, places it into the hidden form fields __EVENTTARGET and __EVENTARGUMENT, and then submits the form, initiating our PostBack.



On the server side, any controls that implement either the IPostBackDataHandler or IPostBackEventHandler interfaces (which means that they can process client-side events) will have the appropriate method called to examine the PostBack data and see if they have raised the client-side event. If so, the corresponding server-side event is raised and the event handler method is called. You can create your own custom PostBack by simply calling the ASP.NET "GetPostBackEventReference" method.

So! Now that we've laid out the basics for how everything works in ASP.NET "under the hood", let's see if we can construct a Server-side utility method that can be used from any Page to determine the source of a PostBack. First, the code, then, some notes. What you are seeing below is your Global.asax.cs codebehind file:

using System;
using System.Collections;
using System.ComponentModel;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;

namespace WhichControlPostedBack 
{
 public class Global : System.Web.HttpApplication
 {
  private System.ComponentModel.IContainer components = null;

  public Global()
  {
   InitializeComponent();
  } 
  
public static System.Web.UI.Control GetPostBackControl(System.Web.UI.Page page)
 {
  Control control = null;
  string ctrlname = page.Request.Params["__EVENTTARGET"];
  if (ctrlname != null && ctrlname != String.Empty)
  {
   control = page.FindControl(ctrlname);
  }
  // if __EVENTTARGET is null, the control is a button type and we need to 
  // iterate over the form collection to find it
  else
  {
   string ctrlStr=String.Empty;
   Control c=null;
   foreach (string ctl in page.Request.Form)
   {
    // handle ImageButton controls ...
    if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
    {
     ctrlStr = ctl.Substring(0,ctl.Length-2);
     c= page.FindControl(ctrlStr);
    }
    else
    {
     c = page.FindControl(ctl);
    }
          if (c is System.Web.UI.WebControls.Button || 
                   c is System.Web.UI.WebControls.ImageButton)
    {
     control = c;
     break;
    }
   }
  }
  return control;
 }  
  
  private void InitializeComponent()
  {    
   this.components = new System.ComponentModel.Container();
  }
  
 }
}

What does this do? Well, first, its a static method - which means you can reference it without a class instance, simply by calling Global.GetPostBackControl(this), with the "this" being a reference to the Page class that you are in. Typically we would do this as follows, in the Page_Load event handler method:

private void Page_Load(object sender, System.EventArgs e)
{ 
if(IsPostBack)
Label1.Text=Global.GetPostBackControl(this).ID.ToString();
} 

Note that in the above example (which is used in the sample downloadable code) I am using the Control reference returned by the method just to display that control's ID property as a "proof of concept". However, you have, at this point, a valid instance of the control that actually generated the PostBack to work with, so you could do quite a bit more. The next thing it does is try to see if there is anything in the __EVENTTARGET hidden form field. If there is, it doesn't have much more work to do than find the control and return it. If not, it needs to iterate over the FORM collection to try and extract information about Button types, which as mentioned, don't populate __EVENTTARGET.

In the sample above, you can see that the downloadable demo has a textbox, a dropDownList, an ImageButton (google), a RadioButton, a Checkbox, two Button Controls, and a Linkbutton, all of which are configured to generate a postback event. In the pic above, I have just clicked on the Google ImageButton, and you can see the Label control at the top displaying it's ID property. If I forgot one, let me know below and I'll revise the code to accomodate!

 

The Quirks

The main reason for all the extra code in the GetPostBackControl method is because button type controls DO NOT run the "__doPostBack" script method. All they actually do is SUBMIT the form. This is a cause of considerable confusion among both the uninitiated and the expert. In addition, ImageButton type controls sport an additional "quasi-property" in their Id which identifies mouse x and y coordinates, hence the extra code for the if (ctl.EndsWith(".x") || ctl.EndsWith(".y")) section.

The nice upshot of all this little "research project" is that you can stick this into your solution's Global.asax codebehind file and it won't bother anybody until you need it. Enjoy!

Download the Visual Studio.NET 2003 solution that accompanies this article

 

 

 

Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: