好久没写了。接着前面的来:
一个小Forum Web程序示例,ASP.NET MVC Framework,总体结构介绍(Part 1)
一个小Forum Web程序示例,ASP.NET MVC Framework,TDD简介(Part 2)
上一部分写得太长太详细,这次争取描述清楚的前提下,写简短一点。
上部分中,简单介绍了TDD流程,并创建了一些测试,这部分我将进一步完善,开始实现真实的SqlFourmRepository。
另外简单介绍一个延迟加载的LazyList<T>和MVC分页很方便的PagedList<T>(末尾处有改进的PageList<T>)。
按照惯例,先放个数据库结构图:
还是一如既往的简单,使用了Membership,创建了数据库关系。
这里描述一下:Post表中的ParentId如果为Guid.Empty(00000000-0000-0000-0000-000000000000),表示这个帖子是主贴,如果为其他Post的Id,表示回帖,其他一概从简。
下面是当前的IForumRepository,定义了一些方法,并在SqlForumRepository中实现这些没有被实现的方法。
打开dbml文件,把需要用的几个表,都拖进去:
再看看我们之前的通用Models,这两种实体是不同的,在ForumService中,需要的是Models.Post和Models.Category,在SqlForumRepository中,我们需要查询LinqToSqlClasses实体,而返回的却是Model.Post(需要做一个转换)。
以AddPost(Models.Post post)为例,现在可以开始编写测试了:
这个测试中,创建了一个Post,并尝试提取它(GetPost方法我之前已经实现了)。
在Assert中,严谨的做法应该是对比这两个对象(创建的post和从数据库中提取的actual),不过Assert.AreEqual方法对比引用类型总是会亮红灯,只有重写Post类的Equals方法,在里面分别对比每个属性才行;或者在测试中一个一个去Assert,这样太麻烦了。仅仅是为了测试重写Equals方法,我的做法是:调试这个测试方法,在调试的时候查看是否有漏掉的,没有问题的话,就简单的Assert一下。(不知道正确否?请各位前辈们指点。)
现在运行这个测试,亮红灯,因为方法还没实现。
现在实现这个方法,测试,通过了。
现在如法炮制,实现IForumService的其他所有方法,并保证测试通过就行了。
注意:
含有一个Models.Post的列表,我这里使用的是LazyList<Models.Post>类型(有关LazyList请看这里:Lazy Loading With The LazyList),它不会像ToList()那样被执行,仍然维持IQueryable状态,不会立即查询数据库,LazyList很适合用来处理一对多延迟查询的情况。
这样的查询像这样写就完全OK了(这里的GetPost同样返回的IQueryable<Models.Post>):
还有一种多对一的情况。通过Post反向查找Category,如果仍然用LazyList会让人和迷惑,所以我也很迷惑,写个延迟加载的Category?晕乎乎。所以就干脆省略掉,Post中始终是有CategoryId的。
LazyList<Post>的分页怎么办?
这里有个古老的MVC分页PagedList<T>,在很早之前Scott Hanselman的视频里就是用了这个类,不过它是继承List<T>的,所以它不会被延迟执行。在我们这个方案中,它不应该属于SqlForumRepository,不过这里顺带一起介绍了吧。
这个PagedList<T>(原版)有点小bug(判断分页那里),嘿嘿,还有一种改进型的PagedList<T>,因为不记得在哪里找到的了,所以我直接贴出来:
{
int TotalCount { get; set; }
int PageIndex { get; set; }
int PageSize { get; set; }
bool IsPreviousPage { get; }
bool IsNextPage { get; }
}
public class PagedList<T> : List<T>
{
public PagedList(IQueryable<T> source, int index, int pageSize)
{
TotalCount = source.Count();
PageSize = pageSize;
PageIndex = index;
AddRange(source.Skip((index - 1)*pageSize).Take(pageSize).ToList());
int pageResult = 0;
for (int counter = 1; pageResult < TotalCount; counter++)
{
pageResult = counter*PageSize;
TotalPages = counter;
}
}
public int TotalPages { get; set; }
public int TotalCount { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public bool HasPreviousPage
{
get { return (PageIndex > 1); }
}
public bool HasNextPage
{
get { return (PageIndex*PageSize) < TotalCount; }
}
}
public static class Pagination
{
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
{
return new PagedList<T>(source, index, pageSize);
}
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index)
{
return new PagedList<T>(source, index, 10);
}
public static PagedList<T> ToPagedList<T>(this LazyList<T> source, int index, int pageSize)
{
return new PagedList<T>(source.AsQueryable(), index, pageSize);
}
public static PagedList<T> ToPagedList<T>(this LazyList<T> source, int index)
{
return new PagedList<T>(source.AsQueryable(), index, 10);
}
}
改进版的首先是让页面从1开始,而不是0开始,让用户不会因为第一页是0而迷惑,第二是改变原先的IsNextPage方法名称为HasNextPage,我这里采用的改进版,并添加了两个针对LazyList的扩展方法。
暂时写到这里吧,感觉好像讲得不是很明白。ForumService没什么写头,好像剩下的就只有Controller和Membership了,介绍这方面的帖子很多,我想通过TDD(发挥MVC的优势)这样的角度来写吧,并和大家探讨一下使用Moq测试框架。