因为在线考试必须在Web上进行而不是在本地进行。所以要想学生能在浏览器里进行考试必须将Paper对象转换成html格式才行。除了这个还有一些问题。那就是如何让学生能够在考试期间保存试卷和提交试卷。前面提到了本系统不是通过Asp.net控件来绑定试题的。而是通过将Paper对象转换成XML格式,再通过xslt转换成html的。这样可以方便地生成想要的html但是却无法利用Asp.net视图状态在浏览器向服务器回传时候保存试卷的状态。也就是说每当学生通过Post请求向服务器保存答案的时候,要想学生正在做的试卷上的答案依然存在。我们必须为学生重新呈现学生的试卷,而且还要将以提交的答案再绑定到重新呈现的试卷上。这样学生提交保存答案引起页面刷新后才不至于前面做的答案全都没了。每次学生保存答案都要重新生成整张试卷并绑定学生答案。这样无疑非常的消耗资源。如何能避免每次保存答案不重新生成试卷而只是保存答案呢?也就是说如何在浏览器里提交答案而不刷新整个考试页面。答案就是Ajax!可以利用XMLHttpRequest对象在浏览器后台向服务器发出异步的Post请求提交答案而不用刷新整个页面。下图4-17说明如何保存答案的。
图4-17 保存答案顺序图
学生点击保存试卷按钮。在浏览器端先收集学生表单上的答案。然后向服务器发送异步的Post请求保存答案。服务器收到请求后从学生的表单中收集并保存答案。然后返回给浏览器保存信息。浏览器接到异步请求返回信息后提示学生保存结果。
下面是保存按钮的click事件处理函数
{
var wRequest = new Sys.Net.WebRequest();
wRequest.set_url("SaveAnswer.ashx");
wRequest.set_httpVerb("POST");
wRequest.add_completed(onSaveAnswerCompleted);
var requestBody = getRequestBody($get("form1")) ;
wRequest.set_body(requestBody);
wRequest.get_headers()["Content-Length"] = requestBody.length;
wRequest.invoke();
$get("SaveProgress").innerHTML="正在保存..!" ;
}
注意这里的javascript代码用到了微软的Ajax.net客户端javascript类库。这样我们就不必处理浏览器兼容问题。并且有类库的支持代码看起来更加的简洁。该函数启动一个对SaveAnswer.ashx的异步请求。并通过getRequestBody方法获取Post内容字符串。并注册了请求返回后的回调函数onSaveAnswerCompleted用于提示用户保存成功。
下面是收集学生答案用到的函数{
var aParams=new Array();
for(var i=0;i<oForm.elements.length;i++)
{
var element=oForm.elements[i];
if(element.type=="radio" || element.type=="checkbox")
{
if(element.checked==false)
continue;
}
var sParam=encodeURIComponent(element.name);
sParam+="=";
sParam+=encodeURIComponent(element.value);
aParams.push(sParam);
}
return aParams.join("&");
}
因为Post请求中回传的数据必须用name=value的格式。左边name是数据的名称。右边value是数据的值。另外多个数据要用&隔开。比如我们想要回传学生的姓名和年龄两个数据。Post的内容就应该是 “StudentName=hanjie&StudentAge=24 “。该函数的作用就是取出表单中的所有input的名和值。名就是input的name属性。值就是学生答案。这里的name就是Question的Id 号。最终把整个试卷的答案拼接成一个Post字符串并返回。
下面看看服务器端的SaveAnswer.ashx是如何收集和保存答案的。我们知道由于的Post回来的数据都会在Request.Form集合中。该集合是一个字典可以遍历每个key和value,因为我们是用Question的Id号作为input的name属性的,所以在Form集合中如果key是一个数字就说明这是一个试题的Id,对应的value也就是学生的答案。然后就可以保存答案了。
下面是保存答案的PaperHelper类的SaveAnswer方法
{
QuestionService service = new QuestionService();
foreach (String key in context.Request.Form.AllKeys)
{
long questionId = 0;
if (long.TryParse(key, out questionId))
{
service.GetById(questionId).StudentAnswer = context.Request.Form[key];
}
}
}
下面给出SaveAnswer.ashx的实现。
public class SaveAnswer : IHttpHandler {
public void ProcessRequest (HttpContext context) {
try
{
PaperHelper.SaveAnswer(context);
context.Response.Write("保存成功!
时间:"+DateTime.Now);
}
catch (Exception e)
{
context.Response.Write(e);
}
}
}
该Handler保存答案并返回保存信息。下面给出浏览器的处理服务端端返回信息的onSaveAnswerCompleted函数的实现。
{
if(executor.get_responseAvailable())
{
$get("SaveProgress").innerHTML=executor.get_responseData();
}
}
该函数简单的在页面上显示服务器返回的信息。
下面介绍交卷是如何实现的。交卷的实现比保存答案简单我们直接向SubmitPaper.ashx提交试卷就行了。但是要想实现自动交卷就又要使用javascript来实现了。首先要想自动交卷就必须有自动计时的能力,当考试时间结束时候调用自动交卷功能。
先看看手工交卷是如何实现的。
{
if(window.isTimeout==false)
{
if(confirm("真的要交卷?")==false)
{
evt.preventDefault();
evt.stopPropagation();
}
}
}
交卷会提示用户是否真的要交卷。如果不是则不交卷。注意window.is Timeout是一个全局标识。当页面刚加载时候会被设定为false,在计时函数里,当考试时间结束时会被设为true。下面看看计时函数
{
var timeString=$get("EndTime").value;
var times=timeString.split(/[-:\s]/);
var endTime=new Date(times[0],times[1]-1,times[2],times[3],times[4],times[5]);
var now =new Date()
var difference=endTime.getTime()-now.getTime();
var min=Math.floor(difference / (1000*60));
var sec=Math.floor((difference-min*60*1000)/1000);
$get("Time").innerHTML="剩余时间:"+min+"分"+sec+"秒";
if(min<=4&&window.isAlert==false)
{
alert("考试时间还有5分钟,时间结束系统将自动交卷!");
window.isAlert=true;
}
if(min<=0 && sec<=0)
{
window.isTimeout=true;
$get("SubmitButton").click();
}
}
该函数在页面加载时候被设定为每秒调用一次。用来计时显示考试的剩余时间。如果考试剩下小于5分钟会提示用户。但是只提示一次。通过window.isAlert全局变量来记录是否已经提示过了。当考试时间结束会触发交卷按钮的click事件来自动的交卷。调用前把window.isTimeout设为true这样在调用submitPaperButton_click(evt)就不会在出现是否要交卷的提示。下面给出页面在浏览器加载时候所有的事件处理函数和全局标识是如何注册的。还有页面关闭时候事件是如何取消的。
Sys.Application.add_unload(page_unload);
function page_load()
{
SubmitButton=$get("SubmitButton");
SavePaperButton=$get("SavePaperButton");
$addHandler(SubmitButton,"click",submitPaperButton_click)
$addHandler(SavePaperButton,"click",savePaperButton_click);
window.interval=window.setInterval(showTime, 1000);
window.isTimeout=false;
window.isAlert=false;
}
function page_unload()
{
SubmitButton=$get("SubmitButton");
SavePaperButton=$get("SavePaperButton");
$removeHandler(SubmitButton,"click",submitPaperButton_click);
$removeHandler(SavePaperButton,"click",savePaperButton_click);
window.clearInterval(window.interval);
}
以上函数中$get,$addHandler等都是Ajax.net客户端类库的方法。使用类库我们的函数可以在所有主流浏览器中运行良好。