- May
- 10
- 2009
ImageButton: Server-side click-event not fired when in UpdatePanel / ICallBackEventHandler context
Posted by mhofer1976
No Comments »
Now this one took really a lot of my spare time and almost broke my head – so I hope my post will prevent you from going through the same… I still have no clue why and what but if you happen to experience that your ImageButton won't fire the client-side click event, please read this and maybe it helps. This applies also to scenarios where you are using the standard calendar or Grid-controls as they are using internally ImageButtons.
Let's explain the scenario little bit more: I'm talking about using the ImageButton control in an "asynchronous" context. Certain portions of the page area loaded asynchronously by using either an UpdatePanel or by explicitely implementing ICallBackEventHandler. In my case (yes, it sounds weird), I'm using both at the same time: A webpart that contains an update panel, but on the first load of the webpart, I'm loading the contents of the update panel through ICallBackEventHandler. You might ask if I've gone completely mad now – but imagine you have 20 of these webparts on the page, each of them is loading an RSS-Feed that takes at least 2 secs to load. UpdatePanels would not load truly "asynchronously" as they will go through the whole page lifecyle instead of going only through the WebPart / content that needs to be rendered. Using ICallBackEventhandler, I can safe a lot of time and resources. But this concepts I will explain in some upcoming posts more in detail.
Fact is: I load an ImageButton through ICallBackEventHandler, if the user presses on it, the UpdatePanel will fire a partial PostBack. On the first click on the ImageButton, the server-side Click-event is not fired. But UpdatePanel then loads its server-side content normally and renders the ImageButton again. If the user presses again on it, the server-side Click-event is fired…
I cannot explain you exactly what happens and what not – I've compared postback and ViewState information (actually, the ViewState of my WebPart is set to false anyway), everything is completely equal for each post-back. I've started then to look at the ImageButton with .NET Reflector. It's straight forward and not so hard to understand.
First thing I^ve seen is that it is really necessarely to set "enableEventValidation" to false on the Page, respecitvely in the web.config if you want to avoid event validation problems using the ICallBackEventHandler. This is because you would have to register your ImageButton on the page (it will be written into the hidden "__EventValidation"-field and read back on post-back – which is impossible to do in ICallBackEventHandler as you are only posting back the html of your control and don't update hidden page fields (not sure if this applies as weill to UpdatePanels, I've not investigated in this).
But this didn't help yet (I've done it already long time ago when started with ICallBackEventHandler), still the same behaviour… From reading the ImageButton code and analyzing the post-back data i could find in Page.Request.Params, I figured out that the IPostBackDataHandler method LoadPostData is the key to the Click-processing for the ImageButton Control. I've also figured out, that the ScriptManager on my page tells me exactly which control is causing the event through the AsyncPostBackSourceElementID property which happens to be the UniqueID of my ImageButton.
Knowing this all, it is then easy to implement a generic solution for all ImageButtons in your web-application by implementing a very short ControlAdapter that will change the behaviour of the ImageButton-control. Just create the following class:
using System;
using System.Web.UI;
using System.Web.UI.Adapters;
namespace YourNameSpace
{
public class AsyncImageButtonAdapter : ControlAdapter
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (Page != null && Page.IsPostBack && ScriptManager.GetCurrent(Page) != null)
{
string pbSourceElementID = ScriptManager.GetCurrent(Page).AsyncPostBackSourceElementID;
if (!string.IsNullOrEmpty(pbSourceElementID))
{
if (pbSourceElementID == this.Control.UniqueID)
{
((IPostBackDataHandler)this.Control).LoadPostData(this.Control.UniqueID, this.Page.Request.Params);
}
}
}
}
}
}
It doesn't do much: If there is a Page and we are in a PostBack, it checks if there is a ScriptManager and asks it for the PostBackSource. If this happens to be the UniqueID of the adapters ImageButton it explicitely calls LoadPostData – and there you go with your Click-Event!!!
All you have to do now is to add your own .browser file to the App_Browsers directory of your SharePoint web applications (or any other ASP.NET web application) virtual directory:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.WebControls.ImageButton"
adapterType="YourNameSpace.AsyncImageButtonAdapter,YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec7" />
</browser>
</browser>