Cascading Events up the stack

liptonIcedTea

Well-known member
Joined
Jan 18, 2007
Messages
89
Programming Experience
1-3
Hi,

This is a question on code design.

I have an application that uses the Box Model - as in that I have pages, and the pages are constructed by web controls all added within each other to get like a hierarchy of controls.

So for example, i may have a page that looks like this

page - webcontrol1 - webcontrol11 - webcontrol 111 - webcontrol 1111
- webcontrol 112
- webcontrol12 - webcontrol 121
- webcontrol2 - webcontrol21

like a treeview.

My question is that I've noticed that when I need one web control to speak to another, and they are on different parts of the page, i pretty much have to raise an event, and cascade it up all the parent web controls to the page level, then cascade it back up the webcontrols till i get to the destination web control.

Eg. if WebControl1111 needs to speak to WebControl21, I have to raise an event in 111, then 11, then 1, then the page, then 2, then 21

This creates a lot of repetive coding of event handling classes that pretty much just raise another event.

My question is, is there a better way to do this? My idea is some sort of a base class that all my web controls could derive from, where I can handle the cascading of events perhaps with parameters that specify the parameters i want to pass, and which web control i want to take it to.

I just not sure how to go about it. Anyone have any experience with raising events in ASP.NET?

Cheers
 
You can create a shared event in an App_Code class, also a shared method that raises it. For event parameter you can have an object where target and other info is specified. Any control class may subscribe to this event and filter by target which events they wish to handle. Example:
VB.NET:
Public Class EventClass
    Shared Event GlobalEvent(ByVal info As eventinfo)
    Shared Sub RaiseGlobal(ByVal info As eventinfo)
        RaiseEvent GlobalEvent(info)
    End Sub
End Class
Public Class eventinfo
    Inherits EventArgs
    Public target As String
    Public message As String
End Class
sample page listener, page class is "thePage":
VB.NET:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    AddHandler EventClass.GlobalEvent, AddressOf handler
End Sub
Sub handler(ByVal info As eventinfo)
    If info.target = "thePage" Then
        Me.Label1.Text = info.message
    End If
End Sub
a control may call this no matter where is the control hierarchy it is:
VB.NET:
Dim info As New eventinfo
info.target = "thePage"
info.message = Me.ID & " calls thePage"
EventClass.RaiseGlobal(info)
setting up a control to listen is the same, but here you can for example check "If info.target = Me.ID".
 
Hi, one more problem though.

It seems to be calling this event multiple times.

My theory is that there are multiple instances of the class, so therefore each instance fires the event.

Is it possible to have a static class in VB.NET?

Public Static Class <classname> does not work.
 
GlobalEvent is only raised once for each call to RaiseGlobal, but all subscribers will get the event of course. But I don't understand what you are doing or mean with last post.
 
GlobalEvent is only raised once for each call to RaiseGlobal, but all subscribers will get the event of course. But I don't understand what you are doing or mean with last post.

Hi John,

I have done a little further research, and I have realized that this is happening because I'm adding the handlers every time my page reloads.

Is it possible to somehow dispose of the handlers every time I dispose of the page? Is there some sort of "RemoveAllHandler" command in Vb.NET?

I want to do this becuase normally I can't avoid putting the AddHandler function in the Load method because I'm working on a project where I don't have access to the Session_Start or Application_Start method.

Is there a debugging feature in Visual Studio which allows me to see the number of methods that are attached to an event?
 
Last edited:
When I tested this I had to add handler each postback else reference breaks. Each postback the page starts from zero template and rebuild with info from Viewstate. IsPostback is used for controls that support Viewstate to only initialize first time a page loads. If you somehow finds otherwise it would be interesting seeing how.
 
When I tested this I had to add handler each postback else reference breaks. Each postback the page starts from zero template and rebuild with info from Viewstate. IsPostback is used for controls that support Viewstate to only initialize first time a page loads. If you somehow finds otherwise I would be insteresting seeing how.

Really? My understanding is all shared methods, events, properties etc. do not lose their state upon re-load, because you can never unload a shared instance from the AppDomain. This is the same behaviour i'm exhibiting.

Basically, my solution has been to make a custom event like this

VB.NET:
   'list of methods that should fire when ControllerSelected event is raised
    Private Shared marrDelegates As New ArrayList

    'The method signature of each method in the marrDelegates
    Public Delegate Sub controllerSelectedEventHandler(ByVal controllerType As PageControllers, _
                 ByVal args As System.Collections.Generic.Dictionary(Of String, Object))

    Public Shared Custom Event ControllerSelected As ControllerSelectedEventHandler

        AddHandler(ByVal value As controllerSelectedEventHandler)
            Dim blnExist As Boolean = False
            For Each dele As controllerSelectedEventHandler In marrDelegates
                If dele.Method.Name.Equals(value.Method.Name) Then
                    blnExist = True
                End If
            Next
            If (blnExist = False) Then
                marrDelegates.Add(value)
            End If
        End AddHandler

        RemoveHandler(ByVal value As controllerSelectedEventHandler)
            marrDelegates.Remove(value)
        End RemoveHandler

        RaiseEvent(ByVal controllerType As PageControllers, ByVal args As System.Collections.Generic.Dictionary(Of String, Object))
            For Each eh As controllerSelectedEventHandler In marrDelegates
                eh.Invoke(controllerType, args)
            Next
        End RaiseEvent
    End Event
Now when I try to add a handler that is already subscribed to the shared event, it'll reject it and not add it to the arraylist.

This seems to work. Although I do have one small question. Is comparing the method name like I'm doing above, is that a good way to compare delegates? I can't think of any other way, except maybe check hashcode.
 
Last edited by a moderator:
Really? My understanding is all shared methods, events, properties etc. do not lose their state upon re-load, because you can never unload a shared instance from the AppDomain. This is the same behaviour i'm exhibiting.
The shared objects don't loose 'themselves', but the form instance will be new each load, so the handler reference won't be valid. That's the deal with Http being a stateless protocol. Check for example this:
VB.NET:
If Not IsPostBack Then
    Session.Add("instance", Me)
    Me.Title = (Me.Equals(Session("instance"))).ToString
Else
    Me.Title = (Me.Equals(Session("instance"))).ToString
    Session("instance") = Me
End If

Storing multiple delegates in an ArrayList like you do is unnecessary, as delegates are multicast. Same functionality with this:
VB.NET:
Private Shared d As controllerSelectedEventHandler
Public Shared Custom Event ControllerSelected As controllerSelectedEventHandler
    AddHandler(ByVal value As controllerSelectedEventHandler)
        d = System.Delegate.Combine(d, value)
    End AddHandler

    RemoveHandler(ByVal value As controllerSelectedEventHandler)
        d = System.Delegate.Remove(d, value)
    End RemoveHandler

    RaiseEvent(ByVal controllerType As PageControllers, _
               ByVal args As System.Collections.Generic.Dictionary(Of String, Object))
        d.Invoke(controllerType, args) 'invokes all delegates from invocation list in order
    End RaiseEvent
End Event
I left out your duplicate check for simplicity, better can be achieved with this:
VB.NET:
For Each t As controllerSelectedEventHandler In d.GetInvocationList
       If t.Equals(value) Then
It will rule out objects trying to add handlers with same signature from same instance, but not across instances, ie different classes can have same handler signature. But do you really need it? It shouldn't be that hard to see in one class adds only one handler?

In RaiseEvent you can also iterate the invocation list and invoke each delegate, or only a selection if you manage to create such design.

What is also of a concern is that shared objects are application wide, so this is a memory leak. You should therefore reset the delegate each postback, d=Nothing. Note that multiple sessions can't affect each others event handlers (not that I've seen, at least), but more sessions will with time build up a lot of lost delegate references if you don't reset the delegate. (maybe even hold on to multiple discarded page objects? ouch, that would be much memory waste on web server!)

There exist also a special class EventHandlerList to be used to store multiple delegates, one for each different event (and not one for each event handler ;)). You can read about this in How to: Declare Events That Conserve Memory Use
 
Back
Top