Archive for the 'asp.net' Category

Jun 12 2010

cancel all event handlers on postback

Published by Raja Nadar under asp.net, c#

recently i had an interesting problem in my project.. which is an asp.net 3.5 web application. it consists of a monster page, which nests close to million user controls within it. these user controls post back at will, and sometimes whimsically too… the page itself had a RAD Navigation control, the wizard king.

now the one common object these user controls and the page itself, was working was my Entity object from Entity Framework. Now the page is basically a wizard, which keeps filling this object, and there are options to save this object explicitly, or create templated items out of it.

This same object (Business Entity) could also be worked by multiple users across different browser sessions/work stations. sort of a disconnected data set. Any persistent action done on this object updates the ‘Last Modified’ date time of this object. (locally and in the database)

My requirement was that, during every page load, i needed to check if this local object was the latest copy, and if stale, redirect the user to a “no donuts for you” page…

Now, in the normal scenario, i would write a simple method in the page load of my monster page, which checks if the object is latest, and if not, just do a

Response.Redirect(donutUrl, true);

But Murphy being who he is, never gives normal scenarios to developers. I had to pop up a Modal window to the user to inform that, “you are working on a stale entity and need to refresh your view.” And on the click of a confirmation, redirect the user.

So i used an AJAX Modal popup, and popped up the message. On the OK click, the user was redirected as i had wished for.

Now as much as it looks hunky-dory, a weird thing was happening… if the user clicked on the “Save My Object So Far” button, the page load did its task, of popping up the popup properly (tongue twisty?) but the event handler for the “Save” button continued to happen behind, and it saved the entity… that is not what i wish for on a Monday morning, and then the next morning, and then the next…

now i had to prevent any further operation after the popup… so i began to put the stuff between by ears into effect… as lame as they were, i thought of the following ideas: (if they are that)

  • have a boolean flag, and chain it across the event handlers, so that if the entity was modified, do not proceed with the event handling code. (lame…. if there are too many event handlers.. and there were)
  • end the page response, after the popup.. so that no further code gets executed after the page load method. but next to grabbing a candy from my 2 year old cousin quietly, i have not been able to do this.. either a blank page is rendered, or everything happens as unexpected… there are no half-measures with the asp.net page life cycle.. (i cannot use document.Write())
  • how about if i manage to suppress all the event handlers on the page, once i detect that the entity is stale.

the 3rd idea seemed to make sense, and i proceeded with a dummy page.. which rendered a label on page load. and then, a button click modified this label. then in the page load, i tried to do a

protected void Page_Load(object sender, EventArgs e)
{
    this.LabelMessage.Text = "Page Load";
    this.ButtonAction.Click -= this.ButtonAction_Click;
}
 
protected void ButtonAction_Click(object sender, EventArgs e)
{
    this.LabelMessage.Text = "Clicked Me, overwrote Page Load";
}

and holy guacamole… it worked.. the event handler was not executed…

now another problem i noticed, is that i don’t know which control posted back.. also, which event of the control caused the postback.. so i had to solve 2 problems..

  • identify the control that posted back
  • remove all event handlers for that control..

and the doors of a solution seem to open up… finding the postback control was a standard snippet i use, and reflection rocks, when i have to dig out the protected “Events” property of any Control. i also realized that, the event handler delegates are stored in a linked list format for every event… which also need to be retrieved using reflection..

so i wrote a utility method, which, if given a page, finds the control that posted back, and removes all the event handlers of the control.

this way, you have a central generic method, which can suppress any further event handling in your code.

now that i have typed close to two pages of a newspaper, time to post the snippet, which you’ll scroll through, in the first place, in any case.. so here goes nothing…

// Cancels all the event handling code for the control that posted back.
public static void CancelPostbackEvent(Page page)
{
    if (page.IsPostBack)
    {
        var postBackControl = Utility.GetPostBackControl(page);
 
        if (postBackControl != null)
        {
            var controlType = typeof(Control);
 
            var postBackControlEventHandlerList = controlType.InvokeMember("Events", System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
            null, postBackControl, null) as EventHandlerList;
 
            if (postBackControlEventHandlerList != null)
            {
                var eventHandlerListType = typeof(EventHandlerList);
                object headEventHandlerListEntry = eventHandlerListType.InvokeMember("head", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
                null, postBackControlEventHandlerList, null);
 
                if (headEventHandlerListEntry != null)
                {
                    var delegatesDictionary = new Dictionary<object, Delegate[]>();
                    Utility.GetEventHandlersRecursively(delegatesDictionary, headEventHandlerListEntry);
 
                    foreach (var delegateContainer in delegatesDictionary)
                    {
                        for (var index = delegateContainer.Value.Length - 1; index &gt;= 0; --index)
                        {
                            postBackControlEventHandlerList.RemoveHandler(delegateContainer.Key, delegateContainer.Value[index]);
                        }
                    }
                }
            }
        }
    }
}
 
private static Control GetPostBackControl(Page page)
{
    Control postBackControl = null;
 
    var postBackControlName = page.Request.Params.Get("__EVENTTARGET");
 
    if (!String.IsNullOrEmpty(postBackControlName))
    {
        postBackControl = page.FindControl(postBackControlName);
    }
    else
    {
        foreach (string controlName in page.Request.Form)
        {
            var control = page.FindControl(controlName);
 
            if (control is System.Web.UI.WebControls.Button)
            {
                postBackControl = control;
                break;
            }
        }
    }
 
    return postBackControl;
}
 
private static void GetEventHandlersRecursively(Dictionary<object, Delegate[]> delegatesDictionary, object currentEventHandlerListEntry)
{
    if (currentEventHandlerListEntry != null)
    {
        var eventHandlerListEntryType = currentEventHandlerListEntry.GetType();
 
        var eventHandler = (Delegate)eventHandlerListEntryType.InvokeMember("handler", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic,
        null, currentEventHandlerListEntry, null);
 
        object key = eventHandlerListEntryType.InvokeMember("key", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic,
        null, currentEventHandlerListEntry, null);
 
        var nextEventHandlerListEntry = eventHandlerListEntryType.InvokeMember("next", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic,
        null, currentEventHandlerListEntry, null);
 
        if (eventHandler != null)
        {
            var eventDelegates = eventHandler.GetInvocationList();
 
            if (eventDelegates != null &amp;&amp; eventDelegates.Length &gt; 0)
            {
                delegatesDictionary.Add(key, eventDelegates);
            }
        }
 
        if (nextEventHandlerListEntry != null)
        {
            Utility.GetEventHandlersRecursively(delegatesDictionary, nextEventHandlerListEntry);
        }
    }
}

do let me know, if you have a better solution, or if the above code upsets your chihuahua…

my lack of conscience really pricks me, when i write new code..

3 responses so far

Jul 24 2008

WCF, certificates, event logs and silly security exceptions

Published by Raja Nadar under security, wcf

my friend was working on certificate based WCF transport messages. she prototyped a demo, and was testing it out. she kept on hitting the following exception:

Found multiple X.509 certificates using the following search criteria: StoreName ‘My’, StoreLocation ‘LocalMachine’, FindType ‘FindBySubjectName’, FindValue ”. Provide a more specific find value.

the error message could not have been more concise.. I had a look at the code, and there was nothing programmatic to verify. it was all WCF configuration driven [that I like so much J].

the configuration for binding was as follows:

<bindings>

  <wsHttpBinding>

    <binding name=wsHttpEndpointBinding>

      <security mode=Message>

        <message clientCredentialType=Certificate />

      </security>

    </binding>

  </wsHttpBinding>

</bindings>

the configuration for service credentials was as follows:

<serviceCredentials>

  <clientCertificate>

    <certificate storeLocation=LocalMachine storeName=My

                 x509FindType=FindBySubjectName />

    <authentication revocationMode=Online trustedStoreLocation=CurrentUser />

  </clientCertificate>

  <serviceCertificate findValue=rajanadar.com storeName=My

                      storeLocation=LocalMachine

    x509FindType=FindBySubjectName />

</serviceCredentials>

I thought, the search ‘rajanadar.com’ may be returning more than one certificate from the store. (may be due to root certificates etc.., I don’t know)

I checked my certificate store, and gave a specific (unique) Subject Name and tried different things.

no luck, still the same issue.

after a little observation, I read the error message a little more carefully.. (why didn’t I do this the first time?)

Found multiple X.509 certificates using the following search criteria: StoreName ‘My’, StoreLocation ‘LocalMachine’, FindType ‘FindBySubjectName’, FindValue ”. Provide a more specific find value.

it complained of a blank ‘FindValue’

then it struck me that we missed the FindValue for the client certificate, not the service certificate.

The corrected configuration was:

<serviceCredentials>

  <clientCertificate>

    <certificate storeLocation=LocalMachine storeName=My

        x509FindType=FindBySubjectName findValue=uniqueclient.rajanadar.com />

    <authentication revocationMode=Online trustedStoreLocation=CurrentUser />

  </clientCertificate>

  <serviceCertificate findValue=server.rajanadar.com storeName=My

             storeLocation=LocalMachine x509FindType=FindBySubjectName />

</serviceCredentials>

that solved the issue. it was a simple silly mistake. (obviously only after it was caught)

 

the next error I encountered, sounded something like:

Unhandled Exception: System.Net.WebException: The underlying connection was closed: Could not establish secure channel for SSL/TLS.

 

Fortunately, my past sleight of hand on WSE and SSL certificates, quickly reminded me that, when dealing with Web Applications, I need to give sufficient access permissions to the aspnet user account, to the PFX files of the certificates.

I modified the access permissions of the PFX file in question, (yeah the \AppData\Microsoft\Crypto\RSA\MachineKeys path) and the application seemed to work without any more issues. silly things, nonetheless there’s a first time..

 

p.s. the aspnet user account permission issue reminds me of one more classic issue that I encountered most of the times..

[SecurityException: Requested registry access is not allowed.] 

Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)

System.Diagnostics.EventLog.FindSourceRegistration(String source, String machineName, Boolean readOnly)

System.Diagnostics.EventLog.SourceExists(String source, String machineName) +79

System.Diagnostics.EventLog.SourceExists(String source)

 

this is again because, creating a new event log or event source, needs registry write permissions, typically not possessed by the aspnet account.

 

Solution: initially, I used to grant write permissions to the registry keys

(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\NewLog or HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\Source)
 

But then, this is not a good security approach. during the lifetime of the application, it just needs to write to the event logs and never create new ones. Hence I created the new registry keys (effectively new event logs/sources) using my Installers, and granted read permissions to the web application accounts. this sounded good.

 

there’s a solution to every problem; given enough time and sometimes, well, just time…

13 responses so far