最近一个小Web开发项目的总结

一直以来很少写Web开发的具体代码,以前是专门有人写这些,我只管管就行了。后来呢,也很少做Web类型项目了,最近有两个项目,牵扯到Web开发,而现在一个人单干,也找不到人来写这些了,只得自己憋手蹩脚的摸索。下面是这段时间的摸索总结。

一、代码生成器确实好用

以前是很反代码生成器的,但真的使用过后觉得很好用——简单、直接且控制力强。我用的是园子里李天平(http://ltp.cnblogs.com/)的动软代码生成器。在此,先感谢一下。不过,在感谢李天平之前,我必须先感谢一下郭嘉。

动软的好处是简单、直接、开源。针对我的应用,我做了以下改进:

(1)Image的处理问题。代码生成器生成的操作Image部分的代码有小问题,不能插入全部的Image对象,这个手动修改了下。

(2)生成的代码没有 partial 关键字,不方便扩展。我修改成产生的所有类都是partial类。这样做有什么好处呢?就是对于每个类,你可以在不修改生成的源文件的情况下,申明某个类实现了某些接口,以对代码进行复用。

例如,假设Album、News、Knowledge这三个类都具有某些相同的属性(ID,Title,AccountId,ViewCount,CreateTime,UpdateTime,等),在代码生成器生成的文件之外,另建一个目录,放一个Interface.cs的文件:

interface
 1  public   interface  IContent 
 2 
 3      Guid Id {  get set ; } 
 4      String Title {  get set ; } 
 5      String AccountId {  get ; } 
 6      String Caption {  get ; } 
 7      Int32 ViewCount {  get set ; } 
 8      DateTime CreateTime {  get set ; } 
 9      DateTime UpdateTime {  get set ; } 
10 
11 
12  public   interface  IContentVoteable : IContent 
13 
14      Int32 VoteCount {  get set ; } 
15 
16 
17  public   partial   class  Album : IContent 
18 
19       public  String Caption {  get  {  return   " 相册 " ; } } 
20 
21 
22  public   partial   class  News : IContent 
23 
24       public  String Caption {  get  {  return   " 资讯 " ; } } 
25 
26 
27  public   partial   class  Knowledge : IContent 
28 
29       public  String Caption {  get  {  return   " 手记 " ; } } 
30  }
31 

这样就可以对生成的类进行类型“指派”,可用于泛型代码来简化编程。并且这种“指派”是强类型的,可在编译器检查错误的。

二、分页

这是一个老话题了。以前我直接用GridView和ObjectDataSource,结果是当需求变化了痛苦不堪。比如,客户要求用 VS2005 ……

本着拿来主义的原则,拿来了园子里张子阳(http://www.cnblogs.com/jimmyzhang/)的分页代码。感谢张子阳。在感谢张子阳之前,再感谢一下郭嘉。

具体的开发过程中做了一些思考,做出了以下改变:

(1)没有使用分页存储过程,而是在C#代码中进行封装,纳入我的基础库里面。这样做的理由是:

(a)分页代码比较简单,分页的数据库操作比较耗时,作为存储过程对性能提高不明显;

(b)存储过程使用前需要添加进数据库,而在C#端使用则可以省略这一步骤。

(c)方便调用、修改和封装。以下是我写的C#端的数据库分页代码:

代码
 1  public   static  DataTable GetPagerData(String tableName, String returnColumns, String  where , String orderColumnName, Boolean orderDesc, String keyColumnName, Int32 pageSize, Int32 pageIndex,  params  SqlParameter[] whereParams) 
 2 
 3       if  (pageIndex  ==   1
 4      { 
 5           return  GetTopData(pageSize, tableName, returnColumns,  where , orderColumnName, orderDesc, RemoveNull(whereParams)); 
 6      } 
 7       else  
 8      { 
 9          String sql  =  String.Format( " select top {0} {1} from {2} where {3} and {6} not in ( select top {7} {6} from {2} where {3} order by {4} {5} )  order by {4} {5} " , pageSize, returnColumns, tableName,  where , orderColumnName, orderDesc  ==   true   ?   " desc "  : String.Empty, keyColumnName, pageSize  *  (pageIndex  -   1 )); 
10           return  Query(sql, RemoveNull(whereParams)).Tables[ 0 ]; 
11      } 
12 
13 
14  public   static  Int32 GetDataCount(String tableName, String  where params  SqlParameter[] whereParams) 
15 
16      String sql  =  String.Format( " select count(*) from {0} where {1} "  , tableName,  where ); 
17      Int32 result  =  (Int32)GetSingle(sql, RemoveNull(whereParams)); 
18       return  result; 
19 
20 
21  public   static  DataTable GetTopData(Int32 top, String tableName, String returnColumns, String  where , String orderColumnName, Boolean orderDesc,  params  SqlParameter[] whereParams) 
22 
23      String sql  =  String.Format( " select top {0} {1} from {2} where {3} order by {4} {5} " , top, returnColumns, tableName,  where , orderColumnName, orderDesc  ==   true   ?   " desc "  : String.Empty); 
24       return  Query(sql, whereParams).Tables[ 0 ]; 
25 
26  private   static  SqlParameter[] RemoveNull(SqlParameter[] whereParams) 
27 
28      List < SqlParameter >  list  =   new  List < SqlParameter > (); 
29       foreach  (SqlParameter sq  in  whereParams) 
30      { 
31           if  (sq  !=   null ) list.Add(sq); 
32      } 
33       return  list.ToArray(); 
34  }

 在这段代码中,我直接将分页和Top集成在一起了,取第一页时,使用的是Top,以优化性能。同时,也省掉Top调用。这样做的好处,在下面能体现出来。

(2)对于分页部分的代码,我默认进行一下处理:

(a)url的page参数指现在的pageIndex;

(b)url的count参数指查询结果的总数量;

“count” 和 “page ”也可以指定为其它字符串。当是第一页时,去调用 GetDataCount 方法,获得count参数,这个参数直接传递给第二页,第三页……的url,避免查看第二页第三页时,重复获得count,影响性能。

三、封装和复用

通过上面的处理,就很方便对代码进行封装和复用了。

还是以上面的Album,News和Knowledge来说,这三个,编辑都可以从后台加精、推荐、设为热点、锁定、审核通过与否等操作。如何用最简单的代码来实现前台和后台所需要的以下需求呢:

(1)获得全部加精的对象;获得N天内加精的对象;以上可以按不同的排序方式排序;获得审核通过的对象,获得N天内审核通过的对象;

(2)获得全部推荐的对象;获得N天内推荐的对象;以上可以按不同的排序方式排序;获得审核通过的对象,获得N天内审核通过的对象;

(3)获得全部热点的对象;获得N天内热点的对象;以上可以按不同的排序方式排序;获得审核通过的对象,获得N天内审核通过的对象;

(4)获得最新的对象;获得N天内审核通过的对象;

(5)获得浏览量最多的对象;获得N天内浏览量最多的对象;获得审核通过的对象,获得N天内审核通过的对象;

(6)以上检索可指定帐号,也可以是全体帐号。

……

这里假设Album,News和Knowledge在数据库里相关列的列名都是一致的,EnableStatus代表审核通过与否,0代表待审核,1代表审核通过,-1代表审核未通过;IsLocked,IsHot,IsChoiced,IsMarked等编辑的操作项,分别代表是否锁定,是否热门,是否推荐和是否精华。News和Knowledge还有个CategoryId的列,代表所属类别。

以News为例子,代码为:

代码
 1  public   partial   class  NewsService : Orc.HairFashion.BLL.News 
 2 
 3       public   static  NewsService Instance; 
 4 
 5       static  NewsService() 
 6      { 
 7          Instance  =   new  NewsService(); 
 8      } 
 9 
10       public   static   void  UpdateColumnStatus(ICollection < Guid >  ids, String columName,  int  status) 
11      { 
12          DbHelper.UpdateColumnStatus(ids,  " News " , columName, status); 
13      } 
14 
15       #region  分页查找 
16 
17       protected   static  DataTable GetPagerData(String  where , String orderColumnName, Int32 pageSize, Int32 pageIndex,  params  SqlParameter[] whereParams) 
18      { 
19           return  DbHelper.GetPagerData( " News " " * " where , orderColumnName,  true " Id " , pageSize, pageIndex, whereParams); 
20      } 
21 
22       protected   static  Int32 GetDataCount(String  where params  SqlParameter[] whereParams) 
23      { 
24           return  DbHelper.GetDataCount( " News " where , whereParams); 
25      } 
26 
27       public   static  List < News >  SelectData(String accountId, String mark, String mode, Int32 categoryId, Int32 inDays, String orderColumnName, Int32 pageSize, Int32 pageIndex) 
28      { 
29          DataTable table  =  GetPagerData( "  1=1  "   +  Util.BuildSqlByMark(mark)  +  Util.BuildSqlByMode(mode)  +  Util.BuildSqlByCategoryId(categoryId)  +  Util.BuildSqlByCreateInDays(inDays)  +  Util.BuildSqlByAccountId(accountId), orderColumnName, pageSize, pageIndex, Util.BuildSqlParameterByAccountId(accountId)); 
30           return  NewsService.Instance.DataTableToList(table); 
31      } 
32 
33       public   static  Int32 SelectCount(String accountId, String mark, String mode, Int32 categoryId, Int32 inDays) 
34      { 
35           return  GetDataCount( "  1=1  "   +  Util.BuildSqlByMark(mark)  +  Util.BuildSqlByMode(mode)  +  Util.BuildSqlByCategoryId(categoryId)  +  Util.BuildSqlByCreateInDays(inDays)  +  Util.BuildSqlByAccountId(accountId), Util.BuildSqlParameterByAccountId(accountId)); 
36      } 
37 
38       #endregion  
39  }

就只用这简简单单的代码就可以了。这里各个查询选项是“正交”的。accountId 为 null或Empty,则检索全部。mark代表编辑的查询类型,是全部,还是精华还是热门还是最新……,实际上这里最好是传入enum。mode代表审核状态,最好使用enum。这两处没用enum是历史遗留问题。categoryId代表类别id,如果为负数则检索全部的类别,inDays代表检索多少天内的数据,负数代表全部,orderColumnName 是排序列,我默认全部desc了,pageSize 是页大小,pageIndex是页序号。

这部分代码是在我动软代码生成器的源代码,使它生成带partial的类之前修改的。如果现在写,使用泛型的话,可以将上面的代码进一步简化。

然后,写一个公用的控件类,以复用共有代码:

BaseListControl
 1  public   abstract   class  BaseListControl : System.Web.UI.UserControl 
 2 
 3       private  Boolean m_showPager  =   false
 4       private  Int32 m_pageSize  =   12
 5       private  Boolean m_orderByVoteCount  =   true
 6       private  Int32 m_inDays  =   7
 7       private  String m_caption  =   ""
 8       private  String m_viewCaption  =   "点击 "
 9       private  String m_voteCaption  =   "票数 "
10       private  String m_orderBy  =   " CreateTime "
11       private  String m_mark  =   ""
12       private  String m_mode  =   ""
13 
14       public  Int32 Count  =   0
15       public  Boolean ShowPager {  get  {  return  m_showPager; }  set  { m_showPager  =  value; } } 
16       public  Int32 PageSize {  get  {  return  m_pageSize; }  set  { m_pageSize  =  value; } } 
17       public  String Caption {  get  {  return  m_caption; }  set  { m_caption  =  value; } } 
18       public  String ViewCaption {  get  {  return  m_viewCaption; }  set  { m_viewCaption  =  value; } } 
19       public  String VoteCaption {  get  {  return  m_voteCaption; }  set  { m_voteCaption  =  value; } } 
20       public  String OrderBy {  get  {  return  m_orderBy; }  set  { m_orderBy  =  value; } } 
21       public  String Mark {  get  {  return  m_mark; }  set  { m_mark  =  value; } } 
22       public  String Mode {  get  {  return  m_mode; }  set  { m_mode  =  value; } } 
23       public  Int32 InDays {  get  {  return  m_inDays; }  set  { m_inDays  =  value; } } 
24 
25       protected   void  Page_Load( object  sender, EventArgs e) 
26      { 
27           if  (Page.IsPostBack  ==   false
28          { 
29              OnPageLoad(); 
30              InitPager(); 
31              BindData(); 
32          } 
33      } 
34 
35       protected   abstract  PagerControl GetPager(); 
36       protected   abstract  Int32 GetResultCount(); 
37       protected   virtual   void  OnPageLoad() 
38      { 
39      } 
40 
41       protected   void  InitPager() 
42      { 
43          PagerControl pager  =  GetPager(); 
44           if  (ShowPager  ==   false
45          { 
46               if (pager != null
47                  pager.Visible  =   false
48          } 
49           else  
50          { 
51              Int32 count  =  PageUtil.GetCurrentCount( this .Page); 
52               if  (count  <   0 ) count  =  GetResultCount(); 
53 
54               if (pager != null
55                  pager.UrlManager  =   new  Orc.Util.AspDotNet.DefaultUrlManager(count, PageSize,  " page " " count " ); 
56          } 
57      } 
58 
59       protected   void  BindData() 
60      { 
61          Int32 page  =  PageUtil.GetCurrentPage( this .Page); 
62           if  (page  <   1 ) page  =   1
63          BindData(page); 
64      } 
65 
66       protected   abstract   void  BindData(Int32 page); 
67  }

然后是具体的控件:

Controls_NewsList
 1  public   partial   class  Controls_NewsList : BaseListControl 
 2 
 3       private  Int32 m_categoryId  =   - 1
 4       public  Int32 CategoryId 
 5      { 
 6           get  {  return  m_categoryId; } 
 7           set  { m_categoryId  =  value; } 
 8      } 
 9 
10       protected   override  Orc.Util.AspDotNet.PagerControl GetPager() 
11      { 
12           return   this .pager; 
13      } 
14 
15       protected   override   int  GetResultCount() 
16      { 
17           return  NewsService.SelectCount( null this .Mark,  this .Mode,  this .CategoryId,  this .InDays); 
18      } 
19 
20       protected   override   void  BindData( int  page) 
21      { 
22           this .rpList.DataSource  =  NewsService.SelectData( null this .Mark,  this .Mode,  this .CategoryId,  this .InDays,  this .OrderBy,  this .PageSize, page); 
23           this .rpList.DataBind(); 
24      } 
25  }

这样一封装就超级好用,可以显示pager也可以不显示,可以用在分页里,也可以用在栏目的首页,且代码量几乎降低到最低,而性能几乎提高到最高,还可以根据各种参数进行缓存。

四、抱怨

尽管使用了这些技巧,Web开发还是太累。一个月坐下来还没做其它项目三五天赚的钱多,并且技术支持比其它类型项目难得多。做一个Web项目后悔一次,今后,尽量不做了,除非价格很高或自己用。

你可能感兴趣的:(web开发)