CodeTalk
LANGUAGES: VB.NET
ASP.NET VERSIONS: 2.0
Develop a Survey Application: Part III
Invitations, Questions, Statistics, and Security
By Bipin Joshi
In Part II of this series we developed a Web form for managing questions, answer choices, and participants. In this final installment we ll make provisions for sending survey invitations, as well as develop an important Web form that displays survey questions and choices to the end user. Finally, we ll develop an administrative Web form for generating survey statistics.
Sending Survey Invitations
Recollect that in the ManageSurvey.aspx Web form we added a LinkButton titled Send Invitations. An invitation is sent to all the participants of the survey when the administrator clicks on this LinkButton. Let s code this functionality now.
Sending e-mails requires a from address, which we store
in the
Figure 1: Using the
We must store a key named webmasteremail along with its value. We ll use this key further while sending e-mail invitations. We also need to configure the SMTP host and its credentials in the web.config file. Figure 2 shows this markup.
Figure 2: Configuring the SMTP host.
The
Protected Sub DetailsView1_ItemCommand(ByVal sender
As Object, ByVal e
As System.Web.UI.WebControls.DetailsViewCommandEventArgs)
Handles DetailsView1.ItemCommand
If e.CommandName = "Invite" Then
Dim cnn As New SqlConnection(
ConfigurationManager.ConnectionStrings(
"ConnectionString").ConnectionString)
Dim cmd As New SqlCommand("select * from surveyparticipants
where surveyid=@id", cnn)
Dim p1 As New SqlParameter("@id", DetailsView1.SelectedValue)
cmd.Parameters.Add(p1)
Dim da As New SqlDataAdapter
da.SelectCommand = cmd
Dim ds As New DataSet
da.Fill(ds, "participants")
Dim client As New SmtpClient()
For Each row As DataRow In ds.Tables("participants").Rows
client.Send(ConfigurationManager.AppSettings
("webmasteremail"), row("email"),
"Invitation to participate in our survey",
"Please take the survey at the following URL :" & vbcrlf &
http://" & Request.Url.Host & "/survey.aspx?id=" &
DetailsView1.SelectedValue)
Next
Label3.Text = "Invitations sent successfully!"
End If
End Sub
Figure 3: Sending survey invitations.
Recollect that we set to Invite the CommandName property
of the Send Invitations LinkButton. The code checks the CommandName property of
DetailsViewCommandEventArgs and if it is indeed Invite, fetches all the
participants for that survey in a DataSet. The code then iterates through all
the fetched records and sends an e-mail to each participant. The e-mail
contains the URL where the participant can take the survey. To send the e-mail,
we created an instance of the System.Net.Mail.SmtpClient class. The SmtpClient
class has a method named Send that takes four parameters: From, Recipients,
Subject, and Body. The from address is retrieved from the
Taking the Survey
After receiving the invitations, the end users are supposed to take the survey at the specified URL. The survey questions and possible choices are displayed on the Survey.aspx Web form. This Web form receives the survey ID as a query string parameter and renders the corresponding questions.
To develop Survey.aspx, add a new Web form in the root folder of the Web site and name it Survey.aspx. The Web form uses a DataList control to display the questions and the choices. Drag and drop a DataList control and a SQL data source control on the Web form. Configure the SQL data source control to select all the records from the SurveyQuestions table matching the SurveyID passed via query string. Then set the DataSourceID property of the DataList control to SqlDataSource1. Design the ItemTemplate of DataList as shown in Figure 4.
Figure 4: Displaying survey
questions and choices.
Using the DataBindings dialog box, data bind Label3 to display QuestionID and Label2 to display the question. Below the Labels is an instance of RadioButtonList, CheckBoxList, and TextBox, respectively. Depending on the AnswerType of the question, only one of them will be displayed. Finally, there is a HiddenField that is bound with the AnswerType property. The FooterTemplate of the DataList simply contains a Button titled Submit. Listing One shows the complete markup of Survey.aspx.
We need to display the choices in a RadioButtonList, CheckBoxList, or TextBox, depending on the AnswerType of a question. This task is handled in the ItemDataBound event of the DataList. Add the code shown in Figure 5 in the ItemDataBound event handler of the DataList.
Protected Sub DataList1_ItemDataBound(ByVal sender As Object,
ByVal e As System.Web.UI.WebControls.DataListItemEventArgs)
Handles DataList1.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType =
ListItemType.AlternatingItem Then Dim anstype As
HiddenField = e.Item.FindControl("HiddenField1")
Dim questionid As Label = e.Item.FindControl("Label3")
Dim rbl As RadioButtonList =
e.Item.FindControl("RadioButtonList1")
Dim cbl As CheckBoxList = e.Item.FindControl("CheckBoxList1")
Dim txt As TextBox = e.Item.FindControl("TextBox1")
Dim ds As DataSet = GetDataSet(questionid.Text)
Select Case anstype.Value
Case "S"
rbl.Visible = True
cbl.Visible = False
txt.Visible = False
rbl.DataSource = ds
rbl.DataTextField = "Choice"
rbl.DataValueField = "ChoiceID"
rbl.DataBind()
Case "M"
rbl.Visible = False
cbl.Visible = True
txt.Visible = False
cbl.DataSource = ds
cbl.DataTextField = "Choice"
cbl.DataValueField = "ChoiceID"
cbl.DataBind()
Case "T"
rbl.Visible = False
cbl.Visible = False
txt.Visible = True
End Select
End If
End Sub
Figure 5: Displaying choices.
The event handler first checks the ItemType property of the item being data bound. Only if the ItemType is ListItemType.Item or ListItemType.AlternatingItem is the rest of the code executed. Inside the If condition we get a reference to all the controls of the ItemTemplate (i.e., QuestionID Label, RadioButtonList, CheckBoxList, TextBox, and HiddenField) using the FindControl method. Then we fill a DataSet with all the choices for the current QuestionID using a helper function named GetDataSet (discussed later). Depending on the AnswerType (single choice or multiple choices), this DataSet is bound with the RadioButtonList or CheckBoxList, respectively. The remaining controls are kept hidden from the end user.
The GetDataSet helper function accepts a QuestionID as a parameter and returns a DataSet with all the choices for that question. Figure 6 shows the complete code for this function.
Private Function GetDataSet(ByVal id As Integer) As DataSet
Dim cnn As New
SqlConnection(ConfigurationManager.ConnectionStrings
("ConnectionString").ConnectionString)
Dim cmd As New SqlCommand("select * from surveychoices
where questionid=@id", cnn)
Dim p1 As New SqlParameter("@id", id)
cmd.Parameters.Add(p1)
Dim da As New SqlDataAdapter
da.SelectCommand = cmd
Dim ds As New DataSet
da.Fill(ds, "choices")
Return ds
End Function
Figure 6: The GetDataSet helper function.
Figure 7 shows a sample run of Survey.aspx. Note how the RadioButtonList, CheckBoxList, and TextBox are displayed depending on the AnswerType.
Figure 7: Sample run of Survey.aspx.
When the user fills the survey and clicks the Submit button, the answers should be stored in the SurveyAnswers table for later reference. This is achieved by handling the Click event of the Submit button. Add the code shown in Figure 8 in the Click event handler of the Submit button.
Protected Sub Button1_Click(ByVal sender As Object,
ByVal e As System.EventArgs)
For Each item As DataListItem In DataList1.Items
If item.ItemType = ListItemType.Item Or item.ItemType =
ListItemType.AlternatingItem Then
Dim questionid As Integer
Dim choiceid As Integer = 0
Dim choicetext As String = ""
questionid = CType(item.FindControl("Label3"), Label).Text
Dim anstype As HiddenField = item.FindControl("HiddenField1")
Select Case anstype.Value
Case "S"
Dim rbl As RadioButtonList =
item.FindControl("RadioButtonList1")
choiceid = rbl.SelectedValue
SaveAnswer(questionid, choiceid, "")
Case "M"
Dim cbl As CheckBoxList = item.FindControl("CheckBoxList1")
For i As Integer = 0 To cbl.Items.Count - 1
If cbl.Items(i).Selected Then
choiceid = cbl.Items(i).Value
SaveAnswer(questionid, choiceid, "")
End If
Next
Case "T"
Dim txt As TextBox = item.FindControl("TextBox1")
choicetext = txt.Text
SaveAnswer(questionid, 0, choicetext)
End Select
End If
Next
End Sub
Figure 8: Saving survey answers.
The code iterates through all the Items and AlternatingItems of the DataList. With each iteration, QuestionID and AnswerType are retrieved using the FindControl method, as before. If the AnswerType is S , the SelectedValue of the RadioButtonList is retrieved. This answer is stored in the SurveyAnswers table using a helper function named SaveAnswer (discussed later). If the AnswerType is M , the code iterates through the CheckBoxList and each selected option is saved in the table using the SaveAnswer helper function. Finally, if the AnswerType is T , the text entered in the TextBox is saved to the database using the SaveAnswer helper function. Figure 9 shows the SaveAnswer function.
Private Sub SaveAnswer(ByVal qid As Integer, ByVal cid
As Integer, ByVal ct As String)
Dim cnn As New SqlConnection(
ConfigurationManager.ConnectionStrings
("ConnectionString").ConnectionString)
Dim cmd As New SqlCommand("insert into surveyanswers(
QuestionID,ChoiceID,ChoiceText) values(@qid,@cid,@ct)", cnn)
Dim p1 As New SqlParameter("@qid", qid)
Dim p2 As New SqlParameter("@cid", IIf(cid = 0,
DBNull.Value, cid))
Dim p3 As New SqlParameter("@ct", IIf(ct = "",
DBNull.Value, ct))
cmd.Parameters.Add(p1)
cmd.Parameters.Add(p2)
cmd.Parameters.Add(p3)
cnn.Open()
cmd.ExecuteNonQuery()
cnn.Close()
End Sub
Figure 9: The SaveAnswer helper function.
The function accepts QuestionID, ChoiceID, and ChoiceText as parameters. Inside it prepares an INSERT statement with required parameters. Note how the @cid and @ct parameters are created. If the cid method parameter is 0, the @cid parameter inserts a NULL value in the ChoiceID column. Similarly, if the ct method parameter is an empty string, then the @ct parameter inserts a NULL value in the ChoiceText column.
Viewing Survey Statistics
Once the survey campaign has run and participants have submitted their answers, the administrator would like to see the statistics of the survey responses. To provide such functionality, add a new Web form named SurveyStats.aspx in the Admin folder. Figure 10 shows a sample run of this Web form.
Figure 10: Showing survey statistics.
The Web form consists of a DropDownList listing all the surveys. Selecting a survey shows all its objective questions and their choices, and votes for each choice. To design this Web form, drag and drop on the Web form two SQL data source controls, a DropDownList and a DataList. Configure one of the SQL data source controls to select all the records from the Survey table and configure the other to select all records from the SurveyQuestions table with AnswerType equal to S or M and matching the selected SurveyID from the DropDownList. Bind the DropDownList to the first SQL data source control and the DataList to the other SQL data source control. Design the ItemTemplate of the DataList, as shown in Figure 11.
Figure 11: ItemTemplate to display
survey statistics.
Using the DataBindings dialog box, bind Label3 with the QuestionID column and Label2 with the Question column. The BulletedList displays all the choices, along with the votes for each choice. Listing Two shows the complete markup of SurveyStats.aspx. To display the number of votes for all the choices, we need to handle the ItemDataBound event of the DataList. Add the code shown in Figure 12 in the ItemDataBound event of the DataList.
Protected Sub DataList1_ItemDataBound(ByVal sender As Object,
ByVal e As System.Web.UI.WebControls.DataListItemEventArgs)
Handles DataList1.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType =
ListItemType.AlternatingItem Then
Dim questionid As Integer
questionid = CInt(CType(e.Item.FindControl("Label3"),
Label).Text)
Dim cnn As New SqlConnection(ConfigurationManager.
ConnectionStrings("ConnectionString").ConnectionString)
Dim cmd As New SqlCommand("select * from surveychoices
where questionid=@qid", cnn)
Dim p1 As New SqlParameter("@qid", questionid)
cmd.Parameters.Add(p1)
Dim da As New SqlDataAdapter
da.SelectCommand = cmd
Dim ds As New DataSet
da.Fill(ds, "choices")
cnn.Open()
For Each row As DataRow In ds.Tables("choices").Rows
Dim cmdAns As New SqlCommand("select count(*) from
surveyanswers where choiceid=@cid", cnn)
Dim pCid As New SqlParameter("@cid", row("choiceid"))
cmdAns.Parameters.Add(pCid)
Dim count As Integer = cmdAns.ExecuteScalar()
row("Choice") = row("Choice") & " - " & count
Next
cnn.Close()
Dim list As BulletedList =
e.Item.FindControl("BulletedList1")
list.DataSource = ds
list.DataTextField = "Choice"
list.DataBind()
End If
End Sub
Figure 12: Displaying choices along with the number of votes.
We retrieve the QuestionID using the FindControl method. Then we fetch all the possible choices for that question into a DataSet. We iterate through all the choices and for each choice select the total number of records from the SurveyAnswers table. The count is simply appended to the Choice column. Finally, the resultant DataSet is bound with the BulletedList control.
Securing Administrative Pages
In this last section we ll secure the administrative pages we created earlier so that only the administrator(s) can access them. To begin, open the web.config file and add the markup as shown in Figure 13.
...
Figure 13: Enabling Forms authentication.
Here, we set the authentication mode to Forms . We also
set the loginUrl attribute of the
Figure 14: Creating a user.
Note that because we have not configured any specific membership provider, the membership information will be stored in the ASPNETDB.MDF file under the App_Data folder.
Next, add a Web form named Login.aspx and drag and drop a Login control on it. That s it! The site is now protected from anonymous users. Any attempt to access administrative pages directly will redirect the user to the login page, as shown in Figure 15.
Figure 15: Login.aspx in action.
Conclusion
In this final installment we completed the survey application by adding an invitation feature and Web forms to display survey questions and their choices. We also developed an administrative Web form for displaying survey statistics. And lastly, as a security feature, we implemented forms-based authentication that protects the administrative pages.
Here are some suggestions for extending and enhancing the survey application:
- You can add functionality to track survey participants.
- You can add functionality such that a participant can participate in the survey only once.
- The survey statistics can be graphical and can include more details.
- You may consider automating the job of sending invitations to the participants.
- You can add some exception-handling code and custom error pages especially for Survey.aspx.
- You may want to implement role-based security so several levels of users can use the survey data in different ways.
Have fun as you explore new ways to use and customize this application.
The sample code for this series is available for download.
Bipin Joshi is the founder and owner of BinaryIntellect Consulting (http://www.binaryintellect.com), where he conducts professional training programs on .NET technologies. He is the author of Developer s Guide to ASP.NET 2.0 (http://www.binaryintellect.com/books) and co-author of three WROX books on .NET 1.x. He writes regularly for http://www.DotNetBips.com, a community Web site he founded in the early days of .NET. He is a Microsoft MVP, MCAD, MCT, and member of ASPInsiders. He jots down his thoughts about .NET, life, and Yoga at http://www.bipinjoshi.com. He also conducts workshops on Yoga and Meditation, where he helps IT professionals develop a positive personality. You can contact him at mailto:[email protected].
Begin Listing One Markup of Survey.aspx
<%@ Page Language="VB" AutoEventWireup="false"
CodeFile="Survey.aspx.vb" Inherits="_Default" %>
End Listing One
Begin Listing Two Markup of SurveyStats.aspx
<%@ Page Language="VB" MasterPageFile="~/Admin/
AdminMasterPage.master" AutoEventWireup="false"
CodeFile="SurveyStats.aspx.vb" Inherits=
"Admin_SurveyStats" title="Untitled Page" %>
"ContentPlaceHolder1"
Runat="Server"> " "Select Survey
:"> AutoPostBack="True"
DataSourceID="SqlDataSource1" DataTextField="Title"
DataValueField="SurveyID"> "SqlDataSource2"
Width="100%"> Text='<%#
Eval("QuestionID") %>'> ) Text='<%#
Eval("Question") %>'> BulletStyle="Square"> Text="Here are the
survey results :"> Suryey
Statistics
">
ConnectionString="<%$
ConnectionStrings:ConnectionString %>" SelectCommand="SELECT [SurveyID], Develop a Survey Application: Part III FROM
[Survey]">
ConnectionString="<%$
ConnectionStrings:ConnectionString %>" SelectCommand="SELECT * FROM [SurveyQuestions] WHERE (([SurveyID] = @SurveyID)
AND ([AnswerType] <>
@AnswerType))"> Name="SurveyID"
PropertyName="SelectedValue" Type="Int32" /> Type="String" />
End Listing Two