How will you make it if you never even try?

September 24, 2006

How to Hook into ASP.Net Page Rendering Process to Solve Double Submission Problem (Part 2 of 2)

Filed under: ASP.NET — Tags: — charlieflowers @ 10:59 pm

This is part 2 of a series of posts about how to solve the “double submission” problem on ASP.NET websites. This is based on some work I did in ASP.NET 1.1, and some of the details would be different for ASP.NET 2.0. Part 1 of the series is here.

If you recall, the first post stated that we need to hook into the page rendering process in order to do two things:

1) Wrap the contents of the form into a div that will initially start out as visible. (Let’s call it ‘pageContentsDiv’).

2) But also inject another div, called the ‘pleaseWaitDiv’, into the form. This div says “Please wait, your request is being processed…”, but initially starts out as invisible.

So, how do we hook into the rendering process and accomplish this?

Well, we don’t want to repeat this work in a lot of different pages, of course. Most or all pages in a site will probably need this feature, so let’s make a class that inherits from System.Web.UI.Page. We’ll call it “PleaseWaitBasePage”. Then, we need to override the System.Web.UI.Page.RenderChildren method.

So, here’s how that would look:


Public Class PleaseWaitBasePage
    Inherits System.Web.UI.Page

    Protected Overrides Sub RenderChildren(ByVal writer As System.Web.UI.HtmlTextWriter)

        ‘ Insert code here to inject the controls into the page.

        ' Then, proceed with normal rendering of children.
        MyBase.RenderChildren(writer)
    End Sub

End Class

The first step that we need to do inside the override is to search through the page’s control collection and find the form control. Here’s some VB.NET code that does that trick. Pretty straightforward.

Private Function FindHtmlFormFromPageCollection(ByVal pageControlCollection As ControlCollection) As HtmlForm

   Dim controlCount As Integer = pageControlCollection.Count - 1

   Dim result As HtmlForm

   For index As Integer = 0 To controlCount
       Dim thisControl As Control = pageControlCollection.Item(index)

       If (thisControl.GetType.IsAssignableFrom(GetType(HtmlForm))) Then

           ' ASP.NEt dictates that there can only be one control derived 
		   ' from HtmlForm on any ASP.Net page. So if you found one, it is
           '  safe to assume it is the only one.

           result = DirectCast(thisControl, HtmlForm)
           Exit For
       End If
   Next

   Return result
End Function

Once you have that function, call it like this to obtain the form and to deal with the case that no form was found.

Dim formControl As HtmlForm = FindHtmlFormFromPageCollection(controlCollectionForEntirePage)
If (formControl Is Nothing) Then
 Throw New ApplicationException("No HtmlForm control found in asp.net page " & Me.GetType().ToString() & ", but one HtmlForm control is required to utilize the 'Please Wait' feature. “)
End If

Now, you have the one and only server-side form control for the page. This form control, like all ASP.NET controls, can contain controls within itself. The controls you dragged and dropped onto your page will all (usually) be inside the form control. So, to insert a div that surrounds the main contents of your page (which is our first main goal), you need to insert some new controls into the controls collection of the form control. Here’s how…

formControl.Controls.AddAt(0, New LiteralControl("
<div id='pageContentsDiv'>"))
formControl.Controls.Add(New LiteralControl("</div>
"))

We just added 2 new controls. First, we added a control at position zero, meaning that this is now the first control on the form. This is a LiteralControl, which means that when it renders, it will render exactly the HTML we provided. This control simply causes an open div tag to be rendered. Second, we add another div control, this time as the last control in the controls collection, which closes the div tag. Very simple — and yet now the entire contents of the page is automatically placed inside a div that can be made visible or invisible with a simple little blurb of JavaScript.

Now we need to build and inject the ‘pleaseWaitDiv’. Note that this div also needs to go into the controls collection of the form. We will put it first — though of course it doesn’t matter since only one of the divs will be visible at a time. First, we build another LiteralControl that contains the entire ‘pleaseWaitDiv’. Here’s a helper method to do that:

Private Function BuildControlToPutIntoFormForStandardPleaseWaitMessage(ByVal message As String) As LiteralControl

Dim visibilityTextForPleaseWaitPanel As String = “style='DISPLAY:none'"

Dim sb As StringBuilder = New StringBuilder

sb.Append("
<div id='pleaseWaitDiv' " + visibilityTextForPleaseWaitPanel + " >")

sb.Append("<center>")

sb.Append("
&nbsp;
")
 sb.Append("
")

If message.Length = 0 Then
 sb.Append("Your request is being processed ... please wait")
 Else
 sb.Append(message)
 End If

sb.Append("

&nbsp;
")

sb.Append("<IMG id='pleaseWaitImage' alt='Processing, please wait ...' src='" + Page.ResolveUrl("~/images/Your-Animated-Gif-Here.gif") + "'>")
 sb.Append("
&nbsp;
")

sb.Append("</center>")

sb.Append("</div>
")

' Build control for the contents of the form.
 Dim pleaseWaitControl As LiteralControl = New LiteralControl(sb.ToString())

Return pleaseWaitControl

End Function

That’s fairly simple as well. The next step is to write the lines of code that call that helper method, and that then insert the new div into the same form control that we were already working with. The following lines of code do the trick (they go into the RenderChildren method).

Dim controlToInsert As Control = BuildControlToPutIntoFormForStandardPleaseWaitMessage(messageText)

' Then, insert the "please wait" controls into the form (at the top, though it wouldn't matter since we use the style.display attribute to hide/show).
 formControl.Controls.AddAt(0, controlToInsert)

For clarity, here’s the full text of the RenderChildren method with everything we’ve covered so far in it:

Protected Overrides Sub RenderChildren(ByVal writer As System.Web.UI.HtmlTextWriter)

‘ Inject the special stuff into the form.

Dim formControl As HtmlForm = FindHtmlFormFromPageCollection(controlCollectionForEntirePage)

If (formControl Is Nothing) Then
 Throw New ApplicationException("No HtmlForm control found in asp.net page " & Me.GetType().ToString() & ", but one HtmlForm control is required to utilize the 'Please Wait' feature. “)
 End If
 formControl.Controls.AddAt(0, New LiteralControl("
<div id='pageContentsDiv'  >"))
 formControl.Controls.Add(New LiteralControl("</div>
"))

‘ Now, insert the “pleaseWaitDiv” into the form’s control collection.

Dim controlToInsert As Control = BuildControlToPutIntoFormForStandardPleaseWaitMessage(messageText)

' Then, insert the "please wait" controls into the form (at the top, though it wouldn't matter since we use the style.display attribute to hide/show).
 formControl.Controls.AddAt(0, controlToInsert)

' Then, proceed with normal rendering of children.
 MyBase.RenderChildren(writer)
 End Sub

So, there you have it really. We made a base class called PleaseWaitBasePage that overrides the RenderChildren method and “injects” some new controls into the page. Those new controls fulfill 2 purposes: first, they surround the contents of the page with a div, so that the normal page contents can all be made invisible. Second, it also inserts a div containing the message “Please Wait”, along with an animateed gif – although this div is initially invisible. This sets the stage for making the form contents invisible and the please wait div visible just before a postback occurs.

And all pages on our site that need this feature can get it for free by simply inheriting from “PleaseWaitBasePage” instead of from System.Web.UI.Page.

NOTE: The code in this entry is based on some code that had a number of other features in place, and therefore had to be edited to remove extraneous concepts. I am not 100% sure the code compiles, but it does convey the technique and it should be very close to correct compilation. There’s always the possibility that I introduced some typos or other similar errors. My main goal here was to cover the concepts, not provide 100% functioning code.

September 19, 2006

Solving the double-submission problem for ASP.NET websites (Part 1 of 2)

Filed under: ASP.NET — Tags: — charlieflowers @ 1:37 am

A very common problem in applications (both web apps and rich GUI clients) is the “double-submit” problem, or the “multiple-submit” problem. This happens when a user clicks on a button to do some action, and then clicks again before that action is complete. If the action is ordering a product and charging a credit card, for example, the user could be double-billed.

Here’s an overview of how I solved this problem for a web application recently. I hope to post more detail about it soon. (Note: This was for an ASP.NET 1.1 website. Several aspects of the solution would be different for an ASP.NET 2.0 website).

The goal is to prevent users from clicking “submit” (really, any action that causes a postback) more than once, by making the entire contents of the page invisible (and therefore unclickable), and replacing the page contents with a message saying “Please Wait”. To achieve this, I introduced some machinery that hooks into the ASP.NET page rendering process and modifies the rendered HTML such that it contains not only its own contents, but also an invisible div that says “Please wait…” with a little animation. That div is made invisible via the “display: none” CSS setting. To make this machinery convenient to use, it was placed in an abstract base class called “PleaseWaitPage” that inherits from the ASP.NET Page object. The actual content pages that wish to participate in the “please wait” solution then inherit from “PleaseWaitPage”. In addition to injecting the “please wait” div, the PleaseWaitPage class also surrounds the contents of the page itself in a div called “pageContentsDiv”.

Now, when the user submits the form, we simply run some Javascript that sets the “pageContentsDiv” to invisible, and sets the “pleaseWaitDiv” to visible. At this point, it is impossible for the user to click on anything in the contents div.

Of course, nothing is ever quite that easy. There are a whole lot of controls in ASP.NET that can cause postbacks, but which will never execute the Javascript “onsubmit” event of the form. ASP.NET causes these postbacks by calling a client side script function called __doPostback. Anytime that script in a causes a postback, the page is posted back to the server without triggering the form’s “onsubmit” handler.

Therefore, the final obstacle in this approach is to “hijack” the __doPostback function. Say what?

It’s really not as complicated as it sounds (though I don’t have room and time to explain it fully right here). Every function in Javascript is merely a variable that points to a function. For example, if you write the following Javascript function…


function AddThree(x)

{

     return x + 3;

}

… that is actually equivalent to this…


var AddThree = function(x) 

{

   return x + 3;

}

Since it is merely a variable, you can reasign the variable, and you can of course remember the original value of that variable. And that means that you can “hijack” a function, which, in the simplest sense, means that you can say, “Don’t do what this function originally said to do, but instead call my replacement function.” And if you wish, your replacement function can then call the original function after it has done its work (or before, or in the middle of its work).

So, continuing with the AddThree example above, we could do this…


var originalAddThreeFunction = AddThree;

// The next line is REDEFINING the AddThree variable.

AddThree = function(x)

{

   x = x * 2;

   return originalAddThreeFunction(x);

}

Now, if you call AddThree(5), you will no longer get 8. You will get 13, because you have hijacked the function and made it double x before adding 3.

So … back to our original goal. We need to hijack the __doPostback function to make it switch the visibility of the “pleaseWaitDiv” and the “pageContentsDiv”, and then let it proceed with its original task of causing the postback in a way that is consistent with the expectations of ASP.NET.

I haven’t given you enough information to fully acheive this in this post. There are many omitted details — for example, where in the page you should inject the script to hijack __doPostback, and the details of where to hook in to ASP.NET’s rendering of the page. But I have covered the high-level principles here, and I hope to post more in the future. Meanwhile, here are some links I hope you find helpful.

So, hope that helps. Let me know if you have questions, and I hope to follow up with some detail posts soon.

Create a free website or blog at WordPress.com.