The application I work on has a built in email newsletter engine and has been experiencing some performance issues when processing large email subscription lists. As a result we have upgraded it from classic ASP 3.0 to ASP.NET to take advantage of asynchronous processing. As a part of that asynchronous processing we wanted to show the user a progress bar detailing the progress of their job. Enter the r.a.d.Upload control by Telerik. This control comes with a built in progress manager that allows you to monitor the progress of any custom process.
When the user has finished composing their email newsletter they have several options on sending their message. I'll focus on the "final" send to the entire list. When the user clicks "Send Now" the email editor screen hands things over to our pleaseWait page. This is the page that is hosting the progress manager. From what I could determine from the help documentation, the Progress Manager requires the hosting page (pleaseWait) fire a PostBack through a server control capable of firing a postback. The documentation recommends an asp:button control. In our particular case we didn't want the user to have to press another button just to start their job so in steps document.form.submit in a javascript function in the html.
<script language="javascript" type="text/javascript">
function submitForm() {
document.Form1.submit();
}
</script>
That's great you say, but how do I fire the javascript function if there is no user interaction. Well, I attached it in the code behind to the body tag in the onload event. From here we experienced some odd behavior because once the form is submitted, the page_load event is called and the page re-rendered, guess what, the form.submit is called again. Ooops Chuck, you just sent 2 (or more) emails to everyone on the list. That won't bode well for your SPAM ratings. The answer? Remove the onload event in the case of a postback.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Page.IsPostBack Then
body.Attributes.Remove("onload")
ProcessJob()
Else
body.Attributes.Add("onload", "onLoad=submitForm();")
End If
End Sub
As you can see, if the page.IsPostback property is set to true, we process the job. I'm using asynchronous processing so I need a delegate for my business layer object method at the pleaseWait page class level.
'delgate sub for the send function of the mail.sender object
Delegate Sub SendToListHandler()
In the process job method I'm using the basic asychrnous princiapals of using BeginInvoke, checking to see if the asynchronous result IsComplete property is true, then calling end invoke to release the process.
'create the delegate object and bind to the target method
Dim SendHandler As SendToListHandler
SendHandler = AddressOf message.SendtoList
'execute method asynchronously and capture the IAsyncResult object
Dim ar As System.IAsyncResult
ar = SendHandler.BeginInvoke(Nothing, Nothing)
Do While (Not ar.IsCompleted)
'update the progress
If totalRecipients > 0 Then
UpdateProgress("send", messageId)
Else
totalRecipients = message.TotalRecipients
End If
Loop
Try
SendHandler.EndInvoke(ar)
Catch ex As Exception
End Try
As you probably have noticed, I'm looping to see if the ar.IsCompleted is true. If it isn't, then I'm updating the ProgressManager using my UpdateProgress method shown here:
Private Sub UpdateProgress(ByVal Action As String, ByVal MessageId As Integer)
Dim progress As Telerik.WebControls.RadProgressContext = Telerik.WebControls.RadProgressContext.Current
Dim sentCount As Integer = 0
Dim percentComplete As Integer
Dim loopCount As Integer = 0
Try
Do While sentCount <= totalRecipients
If Action = "send" And (loopCount Mod 10 = 0) Then
message.UpdateSentCount(MessageId)
End If
sentCount = message.SentCount
progress("PrimaryTotal") = totalRecipients.ToString
progress("PrimaryValue") = sentCount.ToString
percentComplete = (sentCount / totalRecipients) * 100
progress("PrimaryPercent") = percentComplete.ToString
progress("CurrentOperationText") = "Sending your email...please wait."
loopCount += 1
Loop
Catch ex As Exception
End Try
End Sub
The UpdateSentCount method, SentCount property are all in the business layer. The details are not important, you just need to know that you should update them periodically to give the user information on the asychnronous process's progress. In the case of a large email recipient list, I didn't want to be updating the value on every run through the loop, hence the loopcount MOD 10 = 0. (I update the count every 10 times through the loop).
The rest is pretty easy and follows the Telerik documentation
- Update your web.config file with the appropriate httpHandler and httpModule declarations.
- Create the Telerik.RadUploadProgressHandler.asp in the root of your application.
- Drop the RadProgressArea and RadProgressManager on your page.
- Customize the template if needed. In our case, we wanted to hide some of the default values and compact the area a bit.