Creating Dynamic Web Quizzes using C# and ASP.NET

WebQuizMG.gif
 
Figure 1: Snapshot of the generated Web Quiz.
 
Just when you thought you'd never see another test again, your back in school, sharpening that #2 pencil and blowing away erasure particles. Well maybe not anymore thanks to the power of C# and .NET!  You may still be taking tests, but you probably won't have to go out and buy a sharpener. This article describes how to create a web quiz from the information in a database. For the purposes of this article, I chose to use MS Access, but the code can be altered easy enough to use SqlServer, MySql, Oracle, or whatever your favorite provider happens to be. Below is the Database Schema for our Quiz:
 
WebQuizUMLMG.gif
 
Figure 2: This Access Database was reverse engineered using WithClass 2000
 
The structure of the Database is fairly simple. The questions are stored in the Questions Table along with the answers. The Choices are stored in the Choices table and are pointed to by the QuestionID key. The StatsTable holds the title and NumberOfQuestions. Finally, the Testers table holds the information for the people taking the test. (This table is not utilized in this example, but will be in part II of this article.)   
 
The web page for the quiz is constructed on the fly using Microsoft WebForms, Tables, TableRows, and TableCells. Below is the code that reads the MSAccess data using ADO.NET and creates the table. Note that all of this is done in the PageLoad Event:
  1. private void Page_Load(object sender, System.EventArgs e) {  
  2.     if (!IsPostBack) {  
  3.         //  This is the Initial Page Load.  Draw the Quiz  
  4.         ReadQuizTitle();  
  5.         ReadQuestionsIntoTable();  
  6.         AddSubmitButton();  
  7.     } else {  
  8.         //  The User has pressed the score button, calculate and publish the results  
  9.         HttpRequest r;  
  10.         r = this.Request;  
  11.         bool AreAllAnswered = CalculateScore(r);  
  12.         HttpResponse rs;  
  13.         rs = this.Response;  
  14.         if (AreAllAnswered == false) {  
  15.             rs.Write("You missed a few questions. Go back in your browser and answer them<P>");  
  16.             return;  
  17.         }  
  18.         //  Write the score  
  19.         rs.Write("Your score is " + NumberCorrect.ToString() + " out of " + NumberOfQuestions.ToString() +  
  20.             "<P>");  
  21.         //  Print out the corrected answers  
  22.         for (int num = 0; num < NumberOfQuestions; num++) {  
  23.             if (WrongArray[num].Length > 0)  
  24.                 rs.Write(WrongArray[num]);  
  25.         }  
  26.         //  Rank the User  
  27.         rs.Write(GetRanking());  
  28.     }  

The first half of the if statement is handled when the page first loads. This part is responsible for drawing the quiz. The second half of the if statement is executed after the user presses the Score Button. This part of the if statement will score the test and output a page giving the score, the missed questions, and a ranking.
 
Unfortunately, you can't tell much from the code above. So let's delve into what's happening in the ReadQuestionsIntoTable Routine:
  1. private void ReadQuestionsIntoTable() {  
  2.     // Fill the questions and choices tables in memory  
  3.     DataSet ds1 = new DataSet("questionsds");  
  4.     oleDbDataAdapter1.Fill(ds1, "Questions");  
  5.     oleDbDataAdapter2.Fill(ds1, "Choices");  
  6.     DataTable QuestionsTable = ds1.Tables["Questions"];  
  7.     DataTable ChoicesTable = ds1.Tables["Choices"];  
  8.     // create a data relation between the Questions and Choices Tables  
  9.     // so we can cycle through the choices for each question  
  10.     DataRelation QALink = new DataRelation("QuestionLink", QuestionsTable.Columns["QuestionID"],  
  11.         ChoicesTable.Columns["QuestionID"]);  
  12.     QuestionsTable.ChildRelations.Add(QALink);  
  13.     NumberOfQuestions = 0;  
  14.     // go through every row in the questions table  
  15.     // and place each question in the Table Web Control  
  16.     foreach(DataRow dr in QuestionsTable.Rows) {  
  17.         // create a row for the question and read it from the database  
  18.         TableRow tr = new TableRow();  
  19.         Table1.Rows.Add(tr);  
  20.         TableCell aCell = new TableCell();  
  21.         // get the text for the question and stick it in the cell  
  22.         aCell.Text = dr["QuestionText"].ToString();  
  23.         tr.Cells.Add(aCell);  
  24.         AnswerArray[NumberOfQuestions] = dr["Answer"].ToString();  
  25.         // create a row for the choices and read from the database  
  26.         int count = 0;  
  27.         // go through the child rows of the question table  
  28.         // established by the DataRelation QALink and  
  29.         // fill the choices for the table  
  30.         foreach(DataRow choiceRow in dr.GetChildRows(QALink)) {  
  31.             TableRow tr2 = new TableRow();  
  32.             Table1.Rows.Add(tr2);  
  33.             // create a cell for the choice  
  34.             TableCell aCell3 = new TableCell();  
  35.             aCell3.Width = 1000;  
  36.             // align the choices on the left  
  37.             aCell3.HorizontalAlign = HorizontalAlign.Left;  
  38.             tr2.Cells.Add(aCell3);  
  39.   
  40.             // create a radio button in the cell  
  41.             RadioButton rb = new RadioButton();  
  42.             // assign the radio button to Group + QuestionID  
  43.             rb.GroupName = "Group" + choiceRow["QuestionID"].ToString();  
  44.   
  45.             // Assign the choice to the radio button  
  46.             rb.Text = choiceRow["ChoiceLetter"].ToString() + ". " + choiceRow["ChoiceText"].ToString();  
  47.             // Assign the radio button id corresponding to the choice and question #   
  48.             rb.ID = "Radio" + NumberOfQuestions.ToString() + Convert.ToChar(count + 65);  
  49.             rb.Visible = true;  
  50.             // add the radio button to the cell  
  51.             aCell3.Controls.Add(rb);  
  52.             count++;  
  53.         }  
  54.         // add a table row between each question  
  55.         // as a spacer  
  56.         TableRow spacer = new TableRow();  
  57.         spacer.Height = 30;  
  58.         TableCell spacerCell = new TableCell();  
  59.         spacerCell.Height = 30;  
  60.         spacer.Cells.Add(spacerCell);  
  61.         Table1.Rows.Add(spacer); // add a spacer  
  62.         //  Increment the # of Questions  
  63.         NumberOfQuestions++;  
  64.     }  

The code above uses ADO.NET to cycle through all the questions and choices and place them in the table on the WebForm. The program uses the Table, TableCell, TableRow WebControls to display the questions in a decent format. 
 
After the user fills in the quiz, It's time to score the test. The results are compared against the QuestionTable's answers and then computed and printed during the PostBack( after the Score button is pressed). The program takes advantage of the nice features of the HttpRequest object to extract the test results. The HttpRequest has a Form property that contains all the Key-Value pair information in a nice hash table. The program goes through each of the Keys in the Request and hashes out the results in each Radio Group. The Value passed by the Request contains the testtakers answers:
  1. private bool CalculateScore(HttpRequest r) {  
  2.     // initialize wrong answer array  
  3.     WrongArray.Initialize();  
  4.     // Load up statistic table to get Number of Questions  
  5.     DataSet ds = new DataSet("StatsDS");  
  6.     oleDbDataAdapter4.MissingSchemaAction = MissingSchemaAction.AddWithKey;  
  7.     this.oleDbDataAdapter4.Fill(ds, "StatsTable");  
  8.     DataTable StatsTable = ds.Tables["StatsTable"];  
  9.     NumberOfQuestions = (int) StatsTable.Rows[0]["NumberOfQuestions"];  
  10.     // Load up Questions Table to Get Answers to  
  11.     // compare to testtaker  
  12.     oleDbDataAdapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey;  
  13.     DataSet ds1 = new DataSet("questionsds");  
  14.     oleDbDataAdapter1.Fill(ds1, "Questions");  
  15.     DataTable QuestionTable = ds1.Tables["Questions"];  
  16.     // Load up choices table to print out correct choices  
  17.     DataSet ds2 = new DataSet("choicesDS");  
  18.     oleDbDataAdapter2.MissingSchemaAction = MissingSchemaAction.AddWithKey;  
  19.     oleDbDataAdapter2.Fill(ds2, "Choices");  
  20.     DataTable ChoicesTable = ds2.Tables["Choices"];  
  21.     // make sure all questions were answered by the tester  
  22.     int numAnswered = CalcQuestionsAnsweredCount(r);  
  23.     if (numAnswered != NumberOfQuestions) {  
  24.         return false;  
  25.     }  
  26.     NumberCorrect = 0;  
  27.     NumberWrong = 0;  
  28.     // initialize wrong answer array to empty string  
  29.     for (int j = 0; j < NumberOfQuestions; j++) {  
  30.         WrongArray[j] = "";  
  31.     }  
  32.     // cycle through all the keys in the returned Http Request Object  
  33.     for (int i = 0; i < r.Form.Keys.Count; i++) {  
  34.         string nextKey = r.Form.Keys[i];  
  35.         // see if the key contains a radio button Group  
  36.         if (nextKey.Substring(0, 5) == "Group") {  
  37.             // It contains a radiobutton, get the radiobutton ID from the hashed Value-Pair Collection  
  38.             string radioAnswer = r.Form.Get(nextKey);  
  39.             // extract the letter choice of the tester from the button ID  
  40.             string radioAnswerLetter = radioAnswer[radioAnswer.Length - 1].ToString();  
  41.             // extract the question number from the radio ID  
  42.             string radioQuestionNumber = radioAnswer.Substring(5);  
  43.             radioQuestionNumber = radioQuestionNumber.Substring(0, radioQuestionNumber.Length - 1);  
  44.             int questionNumber = Convert.ToInt32(radioQuestionNumber, 10) + 1;  
  45.             // now compare the testers answer to the answer in the database  
  46.             DataRow dr = QuestionTable.Rows.Find(questionNumber);  
  47.             if (radioAnswerLetter == dr["Answer"].ToString()) {  
  48.                 // tester got it right, increment the # correct  
  49.                 NumberCorrect++;  
  50.                 CorrectArray[questionNumber - 1] = true;  
  51.                 WrongArray[questionNumber - 1] = "";  
  52.             } else {  
  53.                 // tester got it wrong, increment the # incorrect  
  54.                 CorrectArray[questionNumber - 1] = false;  
  55.                 // look up the correct answer  
  56.                 string correctAnswer = ChoicesTable.Rows.Find(dr["AnswerID"])["ChoiceText"].ToString();  
  57.                 // put the correct answer in the Wrong Answer Array.  
  58.                 WrongArray[questionNumber - 1] = "Question #" + questionNumber + " - <B>" + dr["Answer"].ToString() + "</B>. " + correctAnswer + "<BR>\n";  
  59.                 // increment the # of wrong answers  
  60.                 NumberWrong++;  
  61.             }  
  62.         }  
  63.     }  
  64.     return true;  

That's all there is to it!  In part 2 I plan on adding a web form that gets the testers anonymous ID so that the scores can be collected for each test taker and a test curve can be generated. 


Similar Articles