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.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: