考试系统开发总结(二)——实现、代码
这篇主要讲讲怎么实现之前的设计,之前设计有2个问题要首先解决,不然没办法继续搞:1、多层对象结构在asp.net页面上的表达和数据收集 2、多层对象的持久化保存和还原。这两个问题其实都是一个事情:复杂的对象结构如何表现和更新。
首先设计图出来后还是建立数据库、生成O/R mapping,我使用的MultiTierLinqToSql作为DAL,泛化的Linq2Sql底层,控制类Controller只需要继承基类并指定实体类,就可以得到Insert()、Update()、Delete()、GetList()等等方法了,不用写什么代码,超级方便!(关于MultiTierLinqToSql将来会有文章专门介绍)
然后是建立类型,编写类型的属性等等,包括对象间关系全部设置好。
再次是把持久化相关部分都写起来,包括数据库持久化部分和XML持久化。
之后编译完成了就成了下面的这个图,这个图只保留了最核心的部分,其他的太多了就省略掉了。
至此,做了个测试,把testpaper进行了XML持久化保存,得到的XML文件类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-16"?>
<TestPaper xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>测试试卷标题</Title>
<Description>默认的试卷描述说明</Description>
<Point>150</Point>
<Time>60</Time>
<TopicCollection>
<TopicBase xsi:type="RandomTopic">
<Title>测试的一个题目类型(选择题)</Title>
<Type>Choice</Type>
<Count>10</Count>
<EachPoint>5</EachPoint>
<Subject>Other</Subject>
</TopicBase>
<TopicBase xsi:type="RandomTopic">
<Title>问答题</Title>
<Type>Question</Type>
<Count>20</Count>
<EachPoint>2</EachPoint>
<Subject>Management</Subject>
</TopicBase>
</TopicCollection>
</TestPaper>
|
可以序列化了,做到了第一步,后面是更加艰巨,显示渲染到页面,当然是用repeater这种控件,一层层嵌套和绑定,具体操作的比较简单,就是绑定后用item.FindControl()来找到子控件的repeater,再绑定,再找子控件,反正就是3-4层,写几次确实有点麻烦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<asp:Repeater runat="server" ID="rptTopic" OnItemDataBound="rptTopic_OnItemDataBound">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<strong class="topicTitle"><%#Eval("Title") %></strong> (每题<%#Eval("EachPoint")%>分,共<%#Eval("Count")%>题)</li>
<asp:Repeater runat="server" ID="rptChoice">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<uc1:choice ID="choice" runat="server" Choice='<%#Eval("MatterGeneric") %>' />
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
<asp:Repeater runat="server" ID="rptJudge">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<uc3:judge ID="judge" runat="server" Judge='<%#Eval("MatterGeneric") %>' />
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
<asp:Repeater runat="server" ID="rptFillblank">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<uc2:fillblank ID="fillblank" runat="server" Fillblank='<%#Eval("MatterGeneric") %>' />
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
<asp:Repeater runat="server" ID="rptQuestion">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<uc4:question ID="question" runat="server" Question='<%#Eval("MatterGeneric") %>' />
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
|
其中最子一级用了userControl,是具体显示题目内容的,比如选择、填空、判断、问答,由它们完成继承类多态的表达,还有数据的收集。直接把对象绑到子控件的属性上,具体里面是怎么实现就不用调用的页面来操心了,如果想更泛化一点,需要对这种控件要求接口。不过目前是原型设计,要求还不那么高,就硬编码就可以了。
1 2 3 4 5 6 7 8 |
<li> <strong> </asp:Literal runat="server" ID="litTitle"> </strong> </li>
<asp:Repeater runat="server" ID="rpt">
<HeaderTemplate> <ol> </HeaderTemplate>
<ItemTemplate>
<li class="option"> </asp:Label runat="server" ID="lab" AssociatedControlID="cbx" Text='<%#Eval("Content")%>'> <asp:CheckBox ID="cbx" runat="server" /> </li>
</ItemTemplate>
<FooterTemplate> </ol> </FooterTemplate>
</asp:Repeater>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public Choice Choice { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
if (Choice != null)
{
litTitle.Text = Choice.Title;
rpt.DataSource = Choice.Option;
if (!Page.IsPostBack)
{
rpt.DataBind();
}
}
}
|
数据收集有点麻烦,我在绑定的时候在用户控件的中提供了些输入框,但如何采集呢,先想的是用IPostBackXXXX接口,但实现起来不是很靠谱,还有什么ViewState什么的状态,受到Page的IsPostback的限制等等,后来想了个办法,直接在子控件中提供一个Readonly的属性,Page需要获取子控件信息的时候,通过属性去获取,而.net的属性其实是属性方法,会在要求获取时执行,就完成了数据的采集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public List<int> OptionIndexs
{
get
{
List<int> idx = new List<int>();
for (int i = 0; i < rpt.Items.Count; i++)
{
CheckBox cbx = rpt.Items[i].FindControl("cbx") as CheckBox;
if (cbx != null && cbx.Checked)
{
idx.Add(i);
}
}
return idx;
}
}
|
最后,在Page的提交时,只要使用下面的代码,对绑定的子控件进行遍历,就能取得所有子控件对象(对象引用)或者对象属性,进行组合后就可以持久化了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
for (int it = 0; it < rptTopic.Items.Count; it++)
{
Repeater rpt = rptTopic.Items[it].FindControl("rptChoice") as Repeater;
if (rpt != null)
{
for (int i = 0; i < rpt.Items.Count; i++)
{
ASP.ocx_paper_choice_ascx choiceUC = rpt.Items[i].FindControl("choice") as ASP.ocx_paper_choice_ascx;
if (choiceUC != null)
{
Choice choice = ap.TopicCollection[it].MatterCollection[i].MatterGeneric as Choice;
choiceUC.OptionIndexs.ForEach(delegate(int w)
{
choice.Option[w].Answer = true;
});
}
}
}
}
|
至此,最麻烦的两个问题就解决了。核心逻辑都实现后,再对表现层的用户体验进行优化,很快,项目的大头朝下了。