网站 -- 测试驱动
首先声明:阅读该文档时,你重点要关注的不是结果,而是过程。
这是一个关于网站“企业展示”子系统的开发过程。由于篇幅有限,我们就摘取一个栏目的异步加载来做分析。
要看明白这个文档,你必须具备一下知识:
1. Asp.net Mvc preview2 Framework 系列
2. Jquery 官方论坛 和 jquery api 1.2 参考文档
3. Css+div 标准化
5. 了解xp的测试驱动开发 -- (敏捷软件开发 第四章)
好了,现我们就来做实践吧!
一 我们有什么?
首先,我们有一个xhtml标准: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 和一个 jquery-1.2.3 .js
二 期望的客户代码
我们希望在一个网页上写些什么?怎么写能够出现一个清晰的分层效果,让人容易理解你的作品?当然,一个页面越简单越好:
< head >
< title > 测试 -- 霸州供电站 </ title >
<!-- 引用jquery - 1.2 . 3 .js文件 -->
< script src = " js/jquery-1.2.3.js " type = " text/javascript " ></ script >
</ head >
< body >
< div id = " txtList " style = " width:501px " >
</ div >
</ body >
</ html >
页面内容只有一个id="txtList"的div,还引用了一个javascript框架jquery。
接下来,我们要用jquery脚本来异步获取服务器端的一个json数据。(就是这么想的,然后写下代码)
$(document).ready(showMainTB);
function showMainTB()
... {
//我们想象已经有一个javascript形式的类了(叫MainTB的内容栏目),然后创建它
var txt1 = new MainTB($('#txtList'));
//使用jquery异步取得json数据然后传递给MainTB对象(还没有Handler.ashx后台处理文件)
$.getJSON('Handler.ashx',function(jsonData)...{
//显示栏目
txt1.display(jsonData);
});
}
</ script >
很简短的脚本,我们就希望页面脚本这么写。(或许你有更好的写法)
三 构建代码,进行测试
客户端页面需要一个MainTB的javascript类(包括一个display方法)和一个名为Handler.ashx的后台处理文件。
MainTB.js
use jquery.js;
js类,实现主页面的信息栏目内容区域 栏目样式
element 为id为‘txtlist’的dom元素
*/
function MainTB(element) ... {
this._elem = element;
}
/**/ /*利用取得的json数据填充内容,使用字符串拼接方式写一个block*/
MainTB.prototype.display = function (jsonData)
... {
var sb = '';
sb +='<div class="tblock"><div class="w100"><b class="fleft" style="font-size:14px;"><img src="imgs/index/point1.gif" alt="通知公告" /> ';
sb+=jsonData.subject;
sb+='</b><div class="moreimg">...................................................<a href="TextList.aspx"><img src="imgs/index/more.gif" alt="更多"/></a></div></div><hr style="height:2px; line-height:3px;width:100%;color:#0591e7;background:#0591e7;" /><div class="w100"><div class="fleft"><img alt="标题" src="imgs/index/top.gif" /> ';
sb+=jsonData.tit;
sb+='</div><div style="float:right;width:40%; text-align:center;">';
sb+=jsonData.date;
sb+='</div></div><div class="cir2" style="width:469px;background:#687896;"></div><div class="cir2" style="width:471px; "></div><div class="cir2" style="width:473px;"></div><div class="cir2in"><ul>';
for(var i=0; i<jsonData.context.length; i++)
...{
sb+='<li><div class="fleft tbpoint"><div class="fleft"><img alt="标题" src="imgs/index/point2.gif" /> <a href="Text.aspx">';
sb+=jsonData.context[i].titin;
sb+='</a></div><img style="float:right;" alt="新的" src="imgs/index/new.gif" /></div><div class="tbdate">';
sb+=jsonData.context[i].datein;
sb+='</div></li>';
}
sb+='</ul></div><div class="cir2" style="width:473px;"></div><div class="cir2" style="width:471px;"></div><div class="cir2" style="width:469px;background:#687896;"></div></div>';
this.get_elem().append(sb);
} ;
/**/ /* 属性存取器 */
MainTB.prototype.get_elem = function ()
... {
return this._elem;
} ;
MainTB.prototype.set_elem = function (value)
... {
this._elem = value;
} ;
/**/ /* 属性存取器 */
(1). 测试MainTB。首先,我们做了一个类,必须得测试它是否是符合我们的要求。
不妨先把客户代码改一下:
$(document).ready(showMainTB);
function showMainTB()
... {
//创建一个主内容栏目
var txt1 = new MainTB($('#txtList'));
//手动构建一个son数据
Var jsonData = ...{
'subject':'通知公告',
'tit':'标题区域',
'date':'上传时间',
'context':[
...{'titin':'文章标题', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},
...{'titin':'文章标题', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},
...{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'}
]
}
txt1.display(jsonData);
}
</ script >
运行网页还需要引入两个样式表文件com.css和context.css。之后我们就能看到下边的效果sl-1:
图(sl-1)
是不是你期望的样子呢?
(2) 测试异步传输。在我们的原始客户端代码中有一段代码 $.getJSON( 'Handler.ashx' , function (jsonData) )。其中第一个参数指定了处理该异步请求的是一个叫Handler.ashx的后台文件;第二个参数是一个函数,表示完成异步请求后要做的操作,该函数会得到一个后台传过来的json数据。所以,总的一句话“handler.ashx文件要把一个json数据写到response里,好让我们从客户端得到”。
Handler.ashx文件
public void ProcessRequest (HttpContext context) ...{
context.Response.ContentType = "text/plain";
var sb = new System.Text.StringBuilder();
sb.Append( "{");
sb.Append("'subject':'通知公告',");
sb.Append("'tit':'标题区域',");
sb.Append(" 'date':'上传时间',");
sb.Append("'context':[");
sb.Append("{'titin':'文章标题', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'文章标题', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'},");
sb.Append("{'titin':'霸州供电局公告', 'datein':'2008年4月11日 17:15'}");
sb.Append("]");
sb.Append("}");
context.Response.Write(sb.ToString());
context.Response.End();
}
public bool IsReusable ...{
get ...{
return false;
}
}
}
再次测试仍然可以看到图( sl-1)的效果。
(3) 到这一步,我们的工作要做一个质的改变了——应用asp.net mvc框架
Asp.net mvc 框架的第一步就是要我们在Models文件夹中建立数据实体TB.dbml。注意两个表之间的引用,即主键和外键的关系。系统自动生成的类里边主键表里要有一个EntitySet<外键表>类型的属性。
然后在Cotrollers文件夹中建立一个HomeController继承BaseController——这里我们还封装了一个基类BaseController,该个类继承自Controller,并提供了一个json序列化对象的操作(有重载),和一个写Response的方法。(操作步骤可以参看资料提供的blog),具体代码如下:
BaseController.cs:
... {
[NonAction]
public void RenderJson(object obj)
...{
var json = new JavaScriptSerializer();
var str = new StringBuilder();
json.Serialize(obj, str);
ResponseJson(str.ToString());
}
[NonAction]
public void RenderJson(string str)
...{
ResponseJson(str);
}
[NonAction]
protected void ResponseJson(string json)
...{
Response.Clear();
Response.ContentEncoding = Encoding.UTF8;
Response.ContentType = "application/json";
Response.Write(json);
Response.Flush();
Response.End();
}
}
HomeController.cs
... {
public void Default()
...{
RenderView("Index");
}
public void SetAjaxTB()
...{
TBDataContext tb = new TBDataContext();
//test1
//只能获取sql
var data = from col in tb.GetTable<Colum>()
where col.ID == 1
select new
...{
Title = col.Title,
Articles = from art in col.Articles
select new
...{
ID = art.ID,
Title = art.Title,
UpTime = art.UpTime
}
};
//组织一个json数据
var tb1 = new StringBuilder();
foreach (var jsonData in data)
...{
tb1.Append("{");
tb1.Append("'subject':'" + jsonData.Title + "',");
tb1.Append("'tit':'标题区域', 'date':'上传时间','context':[");
foreach (var con in jsonData.Articles)
...{
tb1.Append("{'titin':'" + con.Title + "', 'datein':'" + con.UpTime + "'},");
}
tb1.Remove(tb1.Length - 1, 1);
tb1.Append("]}");
}
RenderJson(tb1.ToString());
}
}
关于SetAjaxTB () 函数的实现,我本想用模式来分离操作。这个函数有三步实现,一个是组成一个查询query;另一个是拼接一个json字符串(这里我们也可以用BaseController里的json对象序列化来实现);第三个已经分出去了,用RendJson(string)把拼接好的json字符串直接写到Response里。
如果吧第一步和第二步分出去,那么他们之间的关联就需要传递一个IQueryable<>泛型参数。如果这样做了,那么在第二个步骤里边 jsonData.Title 就不知道是什么了,这是一个不好的实现,不过解决的办法还是有的(比如,我们第一步不用匿名方法)。
再根据职责分配原则,类HomeController应该负责第一步和第二步吗?初看这两个都不应该是它来负责的操作,如果有个MakeQuery类提供建立查询query,一个MakeTB负责建立json字符串,就能够满足“单一职责原则”,提高了可扩展性和可维护性。
还要做一步改动,客户端js代码中的后台处理文件路径“Handler.ashx”改成“/Home/SetAjaxTB”(Home指代Controller前缀,SetAjaxTB是HomeController的一个[Action]方法)这样就符合asp.net mvc的风格了。
运行,是否符合你的期望呢?
附上css文件和图片(ff和ie7下兼容):
com.cs
div {...} { margin:0px auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
div ul>li {...} { margin-left:20px; line-height:20px;clear:both;}
ul {...} {list-style:none;}
img {...} {border:0px;}
input {...} { height:14px;}
a:link,a:visited {...} {text-decoration:none;}
a:hover {...} {text-decoration:underline;}
a:link {...} {color:#000;}
a:visited {...} {color:Red;}
a:hover {...} {color:Orange;}
a.whair:link {...} {color:#fff;}
a.whair:visited {...} {color:Red;}
a.whair:hover {...} {color:Orange;}
.fleft {...} {float:left;}
.w100 /**/ /*宽度100%*/ {...} {width:100%;}
.margin5 {...} {margin-top:5px;}
.mtxt {...} {margin:10px 20px 10px 20px;letter-spacing:2px;word-spacing:3px;text-indent:28px;text-align:left; white-space:normal; line-height:24px;background:#f9f9f9;}
context.css
body {...} {font:12px 宋体,simsun,sans-serif; text-align:center; vertical-align:middle;}
.fw14 /**/ /*加粗14px的字体*/ {...} {font-size:14px;font-weight:bold;}
.all {...} {width:980px;}
.hair {...} {margin-left:5px;background:#008f99;height:20px;line-height:20px}
#noplaymsg {...} {height:123px; line-height:123px; text-decoration:blink;}
.nav {...} {background:url(../imgs/index/nav_bg.gif);height:29px; line-height:29px;}
.nav_b {...} {width:86px;height:29px;}
.nav_b2 {...} {width:2px;height:29px;line-height:29px;background:url(../imgs/index/nav_bg_r.gif);}
.cir {...} { height:1px; border:#e0e0e0 solid; border-width:0px 1px;}
.cirin {...} {width:249px;border:#e0e0e0 solid; border-width:0px 1px;}
.cir2 {...} { height:1px; border:#687896 solid; border-width:0px 1px;}
.cir2in {...} {width:474px;border:#687896 solid; text-align:left; border-width:0px 1px;}
.left {...} {width:251px;margin-top:5px;}
.lb1 {...} {margin-top:5px;margin-bottom:5px;width:230px;border:solid 2px #999999;text-align:left;}
.lbtit {...} {font-size:14px;border-top:solid 1px #7a7a7a;border-bottom:solid 1px #7a7a7a;background:url(../imgs/index/l_tit_bg.gif);height:29px;line-height:29px;vertical-align:middle;width:100%;}
.lbtxt {...} {border-bottom:solid 1px #fff; border-top:solid 1px #fff; background:url(../imgs/index/l_txt_bg.gif);height:22px;line-height:22px;width:100%;}
.lbtxtimg {...} {float:left;margin-left:30px;width:14px; height:14px; background:url(../imgs/index/go_ahead.gif)}
.txt {...} {width:729px;float:left;}
.map {...} {height:28px;line-height:28px;font-size:14px;margin-top:5px; margin-left:5px;}
.photo {...} {width:100%;height:120px;line-height:120px;vertical-align:middle;text-decoration:blink;margin-top:2px;background-color:Orange;}
.tblock {...} {width:95%;margin:10px auto;}
.moreimg {...} {width:80%;color:Red; text-align:right; float:right;}
.tbpoint {...} {margin-left:5px;width:60%;}
.tbdate {...} {float:right;width:35%;text-align:left;}
.rblock {...} {width:90%;border:solid 1px #cee0f1;margin-top:5px;margin-bottom:5px;}
.rbtit {...} {text-align:left;font-size:14px;width:100%;height:31px;line-height:31px;vertical-align:middle;background:url(../imgs/index/r_tit_bg.gif);}
.rbbg {...} {background:#c8e7f8; text-align:left;}
.foottxt {...} {margin:10px 50px 10px 50px;line-height:24px;}
/**/ /*********** 页面的TList.ascx *************/
.rtxt {...} {width:95%;}