Welcome to AspAdvice Sign in | Join | Help

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.

 

Published Thursday, August 16, 2007 9:19 PM by joteke
Filed under:

Attachment(s): AJAXEnabledWebSite2.zip

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# University Update - AJAX - Mapping of attributes to control properties in ASP.NET - growing to a custom control

# re: Mapping of attributes to control properties in ASP.NET - growing to a custom control

Thanks for the article, I like it. Just why are so complex code to create events? This is a much simpler, but the same functionality. Am I mistaken? public event EventHandler Click; public virtual void RaisePostBackEvent(string eventArgument) { if (eventArgument == "c") { if (Click != null) Click(this, EventArgs.Empty); } }
Tuesday, August 28, 2007 6:33 AM by Mykola

# re: Mapping of attributes to control properties in ASP.NET - growing to a custom control

It's explained for example at:

http://msdn2.microsoft.com/en-us/library/aa719907(VS.71).aspx

See "Optimizing Event Implementation"

Basically it's about how to use storage (memory usage), plus optimized implementation allows you to call event handling methods in async manner.

Tuesday, August 28, 2007 6:56 AM by joteke

# re: Mapping of attributes to control properties in ASP.NET - growing to a custom control

This was new to me, thanks
Tuesday, August 28, 2007 7:58 AM by Mykola

Leave a Comment

(required) 
required 
(required) 
Enter the code you see below