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("
")
sb.Append("
")
If message.Length = 0 Then
sb.Append("Your request is being processed ... please wait")
Else
sb.Append(message)
End If
sb.Append("
")
sb.Append("<IMG id='pleaseWaitImage' alt='Processing, please wait ...' src='" + Page.ResolveUrl("~/images/Your-Animated-Gif-Here.gif") + "'>")
sb.Append("
")
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.
).