views:

298

answers:

3

I had this idea of registering an event in a base custom page on every button. I can check if it was a save or edit and then do an authorization and validation. Derived pages would still handle the click event to do the specifics for saving on that page. I wasn't sure which events would happen first. (but it probably is in order of wireup) If the parent page runs first, I can get expected behavior by setting an IsAuthorized or IsValidated property and checking that in each derived page's click handler.

Anyhow, is there a way to ensure the base page's event handler runs and completes first, or is there any way to salvage this?

EDIT: Am I overlooking a simpler design that doesn't require adding boiler plate code to every edit/save/update button hander in the application? And what is the right way for one event hander to communicate with another? For example the base page needs to communicate success or failure validation, the derived class needs to communicate success or failure of the save so it can be recorded in the audit.

//Base page-- seems like this will work if events happen in order of wireup.
protected override void OnInit(EventArgs e)
{
    foreach (Control possibleButton in Controls)
    {
        if(possibleButton is Button)
        {
            Button button = (Button) possibleButton;
            button.Command += ButtonCommandPreconditions;
        }
    }
    base.OnInit(e);
    foreach (Control possibleButton in Controls)
    {
        if(possibleButton is Button)
        {
            Button button = (Button) possibleButton;
            button.Command += ButtonCommandPostconditions;
        }
    }
}

void ButtonCommandPreconditions(object sender, CommandEventArgs e)
{
        if(e.CommandName=="Save" || e.CommandName=="Edit")
        {
            //Stuff that needs to happen before other handler   
            //Validate, display failures-- maybe set IsValdated property
            //Check for POST/GET, throw exception on GET.
            //Check for ID, throw exception on anonymous user
            //Check for authorization
            //Display authorization failures-- maybe set IsAuthorized property 
        }
}

void ButtonCommandPostconditions(object sender, CommandEventArgs e)
{
        if(e.CommandName=="Save" || e.CommandName=="Edit")
        {
             //Stuff that needs to happen *after* other handler
            //Log save
        }
}

Edit: Modified code to reflect that event handers are supposed to be handled in order of wireup.

+1  A: 

Event handlers are called in the order they're added. So if you can guarantee that the parent handler is added first, you're fine. (I suspect you already know this.)

You might save yourself some headaches by handling Button Command events in your base class, and then delegating them to your derived classes with a new ButtonCommand event implemented therein. The only real change from your existing derived code would be closer attention to the 'sender' parameter to the derived event handler. For example:

class BaseClass
{
    public event CommandEventHandler ButtonCommand = delegate { };

    protected override void OnInit(EventArgs e)
    {
        foreach (Control possibleButton in Controls)
        {
            if (possibleButton is Button)
            {
                Button button = (Button) possibleButton;
                button.Command += AnyButtonCommandHandler;
            }
        }
        base.OnInit(e);
    }

    void AnyButtonCommandHandler(object sender, CommandEventArgs e)
    {
        // validation logic. if fails, return.

        ButtonCommand(sender, e);
    }
}

class DerivedClass : BaseClass
{
    public DerivedClass()
    {
        base.ButtonCommand += base_ButtonCommand;
    }

    void base_ButtonCommand(object sender, CommandEventArgs e)
    {
        if (sender == button1) { ... }
        else if (sender == button2) { ... }
        // etc.
    }
}

You could also consider replacing the ButtonCommand event with protected void OnButtonCommand(object sender, CommandEventArgs e) (or do both); then you can simply override OnButtonCommand in your derived class.

Ben M
+5  A: 

Events are just multicast delegates, so the event handlers would be raised in the order that they were added to the event.

I would advise against this to be honest. Even if it works, it's pretty opaque - you've got two separate event handlers in different classes handling the same event with no apparent tie between them.

It would be a better practice to bubble the event to the parent handler for processing from each of your inherited pages before doing their own processing. That way there is a clear connection, the code is easy to follow, and you don't have to rely on assumptions about event ordering.


EDIT for new questions:

And what is the right way for one event hander to communicate with another?

You should see my answer in this post. Event handlers are really not meant to communicate with one another in ASP.Net (at least for WebControl/page lifecycle event handlers), as there is no guarantee on firing order. If you have event handlers relying on information being set in sibling event handlers, there is probably a flaw in your design.

For example the base page needs to communicate success or failure validation, the derived class needs to communicate success or failure of the save so it can be recorded in the audit.

There's really a couple of ways to handle this. First off, simply make your base class call abstract or virtual methods that can be overridden in the derived pages. During the button click handler in your base page, do all your validation, then call into the abstract "DoSave()" method of your derived pages, and check to make sure it succeeded.

Or, write your own events, and raise them when need be. Create a "Validation" event in your base class, and have your derived pages subscribe to it if they need to respond. Make sure your base class raises it when it's done validating. Create a "Saved" event in your base class, an event handler for it, and have your base class subscribe to it. Have your derived classes raise the event when they are done saving. Now the base class can handle the logging of it.


I would like to add that it seems like you're loading an awful lot of stuff into the base class button clicks. Can you do the anonymous ID and GET/POST check when the page renders, and just not render the buttons if they don't have permission?

womp
+1  A: 

While I am not prepared to offer a solution (as that would require rather involved understanding of your design), aside from telling you to separate your "Save" (and similar events') logic into their own class hierarchy, and call them in one life from the event handlers. However, I can comment on your original question (before your edit in bold). The problem with your setup is that while you can raise events in a specific order, no event in any .Net Framework is guaranteed order of delivery. They may look ordered when you test on your dev machine, but in practical deployment, the moment the system load goes high the events will be the first things to get deferred in favor of other operations. Event arrival to their listeners can essentially become random.

You should never build your logic based on the order in which events are received. Each event must only consider current state, it cannot assume anything about any other event. Your chaining of events will break when you need it the most.

Alex K
Ah ha! This convinces me that my basic idea was flawed. Thanks! That saved me a lot of effort of going down a blind alley.
MatthewMartin
I guess you just needed to hear it from someone else?
womp