Mapping of attributes to control properties in ASP.NET - growing to a custom control
Often I see questions like this asked by developers who are new to ASP.NET.
I have a DIV element which should fire event XYZ on server when clicked. My code is:
<div id="myDiv" onclick="ZYZ('something')">...</div>
<script runat="server">
public void XYZ(string arg) {
//...
}
</script>
Now, I am getting javascript error in browser. Why?
Now let's see...
First: one is that the DIV in this case is a client-side element. It doesn't have runat="server" attribute which would make it alive on the server. Therefore server-side part has no knowledge of it, so nothing cannot happen either. Second: in ASP.NET when you specify for example OnClick="Button1_Click" to a Button control e.g
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" />
This is effectively using what I like to call call On<EventName>="handler_method_name" pattern or syntax. It specifically means that attaching a server-side event handler in declarative markup happens with specific convention, which maps the event to the handler method which is raised when the event is raised by the control. Therefore, writing any attribute in onsomething="makesomethingonserver" manner doesn't necessarily invoke things on server-side so that when event is raised the given method would be called. See, the event has to be an existing server-side event on that control.
Edit: Yes, and On<EventName>="handler_method_name" works only when specific in declarative syntax. If you try adding it in code via Attributes collection that won't work since this syntax is something that page parsing process understands. In code this corresponds to adding event handlers dynamically I've explained about attributes on later section.
Note: event handler method signatures are dictated by the delegates which define event types. Therefore passing arguments from client-side to server-side doesn't work exactly that way.
Reference: Events and delegates
For example that syntax works with a Button because Button has a Click event declared by using EventHandler delegate type.
[C#]
[WebCategory("Action"), WebSysDescription("Button_OnClick")]
public event EventHandler Click;
[VB.NET]
<WebCategory("Action"), WebSysDescription("Button_OnClick")> _
Public Event Click As EventHandler
Therefore when Button's Click event is raised, Button1_Click method would be called, assuming Button1_Click applies to the signature defined by EventHandler. Generally speaking, when parsing and getting the control built, control's attributes in aspx are checked if they map to server-side members of the control, properties and events. If they do match, properties get set, event handlers get hooked and so on, that's some magic done by the ASP.NET page parsing and compilation process.
Note: Do not confuse On<EventName> attribute with On<EventName> protected method of the control. That method is totally different beast, it's used by the control to raise the <EventName> event. It's a standard pattern in .NET to declare virtual method with OnEventName which raises the event. It's useful especially in control-development scenarios.
[C#]
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = (EventHandler) base.Events[EventClick];
if (handler != null)
{
handler(this, e);
}
}
[VB.NET]
Protected Overridable Sub OnClick(ByVal e As EventArgs)
Dim handler As EventHandler = DirectCast(MyBase.Events.Item(Button.EventClick), EventHandler)
If (Not handler Is Nothing) Then
RaiseEvent handler(Me, e)
End If
End Sub
However, if attributes do not map to any event or property, then what? Well, if control implements IAttributeAccessor interface (many of them do, WebControl base class and HtmlControls are examples), it mostly occurs so that if control renders anything meaningful, attributes are rendered to the markup as-is. This is useful to know if you need to output something which ends up hooking javascript event handlers on the client. And that's what happens accidentally when adding attributes which do not have the name link to the server-side event(s) of the control.
So you need to make clear distinction what happens on the client and what happens on the server. ASP.NET makes these lines blur, but they do exist. The fact exists that there's HTTP request and response between the client and the server and therefore not everything is so obvious.
Ok, now what?
Previous scenario could be covered with my favorite subject, a custom control. :-) It's quite trivial to implement a Panel control which when clicked, raises a server-side Click event. I show the C# ANd VB.NET source code in this post, but see the attachment of this post for the sample project (Ajax-enabled web site in VS2005)
[C#]
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// Summary description for PostbackingPanel_CS
/// </summary>
namespace Samples
{
public class PostbackingPanel_CS : Panel,IPostBackEventHandler
{
#region "Click event"
private static readonly string EventClick = "PostBackPanelClick";
public event EventHandler Click
{
add
{
Events.AddHandler(EventClick, value);
}
remove
{
Events.RemoveHandler(EventClick, value);
}
}
protected virtual void OnClick(EventArgs e)
{
EventHandler ev = Events[EventClick] as EventHandler;
if (ev != null)
ev(this,e);
}
#endregion
#region IPostBackEventHandler Members
public virtual void RaisePostBackEvent(string eventArgument)
{
if (eventArgument == "c")
OnClick(EventArgs.Empty);
}
#endregion
#region "Preparing postback stuff for rendering"
protected virtual PostBackOptions GetPostBackOptions()
{
PostBackOptions p = new PostBackOptions(this);
p.Argument = "c";
p.ClientSubmit = true;
//Setting ValidationGroup and PerformValidation properties here
//would generate client-side validation calls
return p;
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "hand");
PostBackOptions p = GetPostBackOptions();
Page.ClientScript.RegisterForEventValidation(p);
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.ClientScript.GetPostBackEventReference(p));
}
#endregion
}
}
[VB.NET]
Imports Microsoft.VisualBasic
Imports System.Web.UI
Namespace Samples
Public Class PostbackingPanel_VB
Inherits Panel
Implements IPostBackEventHandler
#Region "Click event"
'Optimized event implementation - available in v2.0+
'With 1.x you use standard event Public Event Click As EventHAndler
Private Shared ReadOnly EventClick As String = "PostBackPanelClick"
Public Custom Event Click As EventHandler
AddHandler(ByVal value As EventHandler)
Events.AddHandler(EventClick, value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
Events.RemoveHandler(EventClick, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
Dim ev As EventHandler = Events(EventClick)
If Not ev Is Nothing Then
ev.Invoke(sender, e)
End If
End RaiseEvent
End Event
#End Region
#Region "IPostBackEventHandler implementation"
Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
If eventArgument = "c" Then
RaiseEvent Click(Me, EventArgs.Empty)
End If
End Sub
#End Region
#Region "Preparing postback stuff for rendering"
Protected Overrides Sub AddAttributesToRender(ByVal writer As System.Web.UI.HtmlTextWriter)
MyBase.AddAttributesToRender(writer)
writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "hand")
Dim p As PostBackOptions = GetPostBAckOptions()
Page.ClientScript.RegisterForEventValidation(p)
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.ClientScript.GetPostBackEventReference(p))
End Sub
Protected Overridable Function GetPostBAckOptions() As PostBackOptions
Dim p As New PostBackOptions(Me)
p.Argument = "c"
p.ClientSubmit = True
'Setting ValidationGroup and PerformValidation properties
'would generate client-side validation calls
Return p
End Function
#End Region
End Class
End Namespace
I hope this clarified something to someone.