一直以來很少寫Web開發(fā)的具體代碼,以前是專門有人寫這些,我只管管就行了。后來呢,也很少做Web類型項(xiàng)目了,最近有兩個(gè)項(xiàng)目,牽扯到Web開發(fā),而現(xiàn)在一個(gè)人單干,也找不到人來寫這些了,只得自己憋手蹩腳的摸索。下面是這段時(shí)間的摸索總結(jié)。
一、代碼生成器確實(shí)好用
以前是很反代碼生成器的,但真的使用過后覺得很好用——簡單、直接且控制力強(qiáng)。我用的是園子里李天平(http://ltp.cnblogs.com/)的動(dòng)軟代碼生成器。在此,先感謝一下。不過,在感謝李天平之前,我必須先感謝一下郭嘉。
動(dòng)軟的好處是簡單、直接、開源。針對我的應(yīng)用,我做了以下改進(jìn):
(1)Image的處理問題。代碼生成器生成的操作Image部分的代碼有小問題,不能插入全部的Image對象,這個(gè)手動(dòng)修改了下。
(2)生成的代碼沒有 partial 關(guān)鍵字,不方便擴(kuò)展。我修改成產(chǎn)生的所有類都是partial類。這樣做有什么好處呢?就是對于每個(gè)類,你可以在不修改生成的源文件的情況下,申明某個(gè)類實(shí)現(xiàn)了某些接口,以對代碼進(jìn)行復(fù)用。
例如,假設(shè)Album、News、Knowledge這三個(gè)類都具有某些相同的屬性(ID,Title,AccountId,ViewCount,CreateTime,UpdateTime,等),在代碼生成器生成的文件之外,另建一個(gè)目錄,放一個(gè)Interface.cs的文件:
- public interface IContent
- {
- Guid Id { get; set; }
- String Title { get; set; }
- String AccountId { get; }
- String Caption { get; }
- Int32 ViewCount { get; set; }
- DateTime CreateTime { get; set; }
- DateTime UpdateTime { get; set; }
- }
- public interface IContentVoteable : IContent
- {
- Int32 VoteCount { get; set; }
- }
- public partial class Album : IContent
- {
- public String Caption { get { return "相冊"; } }
- }
- public partial class News : IContent
- {
- public String Caption { get { return "資訊"; } }
- }
- public partial class Knowledge : IContent
- {
- public String Caption { get { return "手記"; } }
- }
這樣就可以對生成的類進(jìn)行類型“指派”,可用于泛型代碼來簡化編程。并且這種“指派”是強(qiáng)類型的,可在編譯器檢查錯(cuò)誤的。
二、分頁
這是一個(gè)老話題了。以前我直接用GridView和ObjectDataSource,結(jié)果是當(dāng)需求變化了痛苦不堪。比如,客戶要求用 VS2005 ……
本著拿來主義的原則,拿來了園子里張子陽(http://www.cnblogs.com/jimmyzhang/)的分頁代碼。感謝張子陽。在感謝張子陽之前,再感謝一下郭嘉。
具體的開發(fā)過程中做了一些思考,做出了以下改變:
(1)沒有使用分頁存儲(chǔ)過程,而是在C#代碼中進(jìn)行封裝,納入我的基礎(chǔ)庫里面。這樣做的理由是:
(a)分頁代碼比較簡單,分頁的數(shù)據(jù)庫操作比較耗時(shí),作為存儲(chǔ)過程對性能提高不明顯;
(b)存儲(chǔ)過程使用前需要添加進(jìn)數(shù)據(jù)庫,而在C#端使用則可以省略這一步驟。
(c)方便調(diào)用、修改和封裝。以下是我寫的C#端的數(shù)據(jù)庫分頁代碼:
代碼
- public static DataTable GetPagerData(String tableName, String returnColumns, String where,
- String orderColumnName, Boolean orderDesc, String keyColumnName, Int32 pageSize, Int32 pageIndex, params SqlParameter[] whereParams)
- {
- if (pageIndex == 1)
- {
- return GetTopData(pageSize, tableName, returnColumns, where, orderColumnName, orderDesc, RemoveNull(whereParams));
- }
- else
- {
- 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));
- return Query(sql, RemoveNull(whereParams)).Tables[0];
- }
- }
- public static Int32 GetDataCount(String tableName, String where, params SqlParameter[] whereParams)
- {
- String sql = String.Format("select count(*) from {0} where {1}" , tableName, where);
- Int32 result = (Int32)GetSingle(sql, RemoveNull(whereParams));
- return result;
- }
- public static DataTable GetTopData(Int32 top, String tableName, String returnColumns, String where,
- String orderColumnName, Boolean orderDesc, params SqlParameter[] whereParams)
- {
- 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);
- return Query(sql, whereParams).Tables[0];
- }
- private static SqlParameter[] RemoveNull(SqlParameter[] whereParams)
- {
- List<SqlParameter> list = new List<SqlParameter>();
- foreach (SqlParameter sq in whereParams)
- {
- if (sq != null) list.Add(sq);
- }
- return list.ToArray();
- }
在這段代碼中,我直接將分頁和Top集成在一起了,取第一頁時(shí),使用的是Top,以優(yōu)化性能。同時(shí),也省掉Top調(diào)用。這樣做的好處,在下面能體現(xiàn)出來。
(2)對于分頁部分的代碼,我默認(rèn)進(jìn)行一下處理:
(a)url的page參數(shù)指現(xiàn)在的pageIndex;
(b)url的count參數(shù)指查詢結(jié)果的總數(shù)量;
“count” 和 “page ”也可以指定為其它字符串。當(dāng)是第一頁時(shí),去調(diào)用 GetDataCount 方法,獲得count參數(shù),這個(gè)參數(shù)直接傳遞給第二頁,第三頁……的url,避免查看第二頁第三頁時(shí),重復(fù)獲得count,影響性能。
三、封裝和復(fù)用
通過上面的處理,就很方便對代碼進(jìn)行封裝和復(fù)用了。
還是以上面的Album,News和Knowledge來說,這三個(gè),編輯都可以從后臺(tái)加精、推薦、設(shè)為熱點(diǎn)、鎖定、審核通過與否等操作。如何用最簡單的代碼來實(shí)現(xiàn)前臺(tái)和后臺(tái)所需要的以下需求呢:
(1)獲得全部加精的對象;獲得N天內(nèi)加精的對象;以上可以按不同的排序方式排序;獲得審核通過的對象,獲得N天內(nèi)審核通過的對象;
(2)獲得全部推薦的對象;獲得N天內(nèi)推薦的對象;以上可以按不同的排序方式排序;獲得審核通過的對象,獲得N天內(nèi)審核通過的對象;
(3)獲得全部熱點(diǎn)的對象;獲得N天內(nèi)熱點(diǎn)的對象;以上可以按不同的排序方式排序;獲得審核通過的對象,獲得N天內(nèi)審核通過的對象;
(4)獲得最新的對象;獲得N天內(nèi)審核通過的對象;
(5)獲得瀏覽量最多的對象;獲得N天內(nèi)瀏覽量最多的對象;獲得審核通過的對象,獲得N天內(nèi)審核通過的對象;
(6)以上檢索可指定賬號,也可以是全體賬號。
……
這里假設(shè)Album,News和Knowledge在數(shù)據(jù)庫里相關(guān)列的列名都是一致的,EnableStatus代表審核通過與否,0代表待審核,1代表審核通過,-1代表審核未通過;IsLocked,IsHot,IsChoiced,IsMarked等編輯的操作項(xiàng),分別代表是否鎖定,是否熱門,是否推薦和是否精華。News和Knowledge還有個(gè)CategoryId的列,代表所屬類別。
以News為例子,代碼為:
代碼
- public partial class NewsService : Orc.HairFashion.BLL.News
- {
- public static NewsService Instance;
- static NewsService()
- {
- Instance = new NewsService();
- }
- public static void UpdateColumnStatus(ICollection<Guid> ids, String columName, int status)
- {
- DbHelper.UpdateColumnStatus(ids, "News", columName, status);
- }
- #region 分頁查找
- protected static DataTable GetPagerData(String where, String orderColumnName, Int32 pageSize, Int32 pageIndex, params SqlParameter[] whereParams)
- {
- return DbHelper.GetPagerData("News", "*", where, orderColumnName, true, "Id", pageSize, pageIndex, whereParams);
- }
- protected static Int32 GetDataCount(String where, params SqlParameter[] whereParams)
- {
- return DbHelper.GetDataCount("News", where, whereParams);
- }
- public static List<News> SelectData(String accountId, String mark, String mode, Int32 categoryId, Int32 inDays, String orderColumnName, Int32 pageSize, Int32 pageIndex)
- {
- 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));
- return NewsService.Instance.DataTableToList(table);
- }
- public static Int32 SelectCount(String accountId, String mark, String mode, Int32 categoryId, Int32 inDays)
- {
- return GetDataCount(" 1=1 " + Util.BuildSqlByMark(mark) + Util.BuildSqlByMode(mode) + Util.BuildSqlByCategoryId(categoryId) + Util.BuildSqlByCreateInDays(inDays) + Util.BuildSqlByAccountId(accountId), Util.BuildSqlParameterByAccountId(accountId));
- }
- #endregion
- }
就只用這簡簡單單的代碼就可以了。這里各個(gè)查詢選項(xiàng)是“正交”的。accountId 為 null或Empty,則檢索全部。mark代表編輯的查詢類型,是全部,還是精華還是熱門還是最新……,實(shí)際上這里最好是傳入enum。mode代表審核狀態(tài),最好使用enum。這兩處沒用enum是歷史遺留問題。categoryId代表類別id,如果為負(fù)數(shù)則檢索全部的類別,inDays代表檢索多少天內(nèi)的數(shù)據(jù),負(fù)數(shù)代表全部,orderColumnName 是排序列,我默認(rèn)全部desc了,pageSize 是頁大小,pageIndex是頁序號。
這部分代碼是在我動(dòng)軟代碼生成器的源代碼,使它生成帶partial的類之前修改的。如果現(xiàn)在寫,使用泛型的話,可以將上面的代碼進(jìn)一步簡化。
然后,寫一個(gè)公用的控件類,以復(fù)用共有代碼:
- BaseListControl
- public abstract class BaseListControl : System.Web.UI.UserControl
- {
- private Boolean m_showPager = false;
- private Int32 m_pageSize = 12;
- private Boolean m_orderByVoteCount = true;
- private Int32 m_inDays = 7;
- private String m_caption = "";
- private String m_viewCaption = "點(diǎn)擊";
- private String m_voteCaption = "票數(shù)";
- private String m_orderBy = "CreateTime";
- private String m_mark = "";
- private String m_mode = "";
- public Int32 Count = 0;
- public Boolean ShowPager { get { return m_showPager; } set { m_showPager = value; } }
- public Int32 PageSize { get { return m_pageSize; } set { m_pageSize = value; } }
- public String Caption { get { return m_caption; } set { m_caption = value; } }
- public String ViewCaption { get { return m_viewCaption; } set { m_viewCaption = value; } }
- public String VoteCaption { get { return m_voteCaption; } set { m_voteCaption = value; } }
- public String OrderBy { get { return m_orderBy; } set { m_orderBy = value; } }
- public String Mark { get { return m_mark; } set { m_mark = value; } }
- public String Mode { get { return m_mode; } set { m_mode = value; } }
- public Int32 InDays { get { return m_inDays; } set { m_inDays = value; } }
- protected void Page_Load(object sender, EventArgs e)
- {
- if (Page.IsPostBack == false)
- {
- OnPageLoad();
- InitPager();
- BindData();
- }
- }
- protected abstract PagerControl GetPager();
- protected abstract Int32 GetResultCount();
- protected virtual void OnPageLoad()
- {
- }
- protected void InitPager()
- {
- PagerControl pager = GetPager();
- if (ShowPager == false)
- {
- if(pager!=null)
- pager.Visible = false;
- }
- else
- {
- Int32 count = PageUtil.GetCurrentCount(this.Page);
- if (count < 0) count = GetResultCount();
- if(pager!=null)
- pager.UrlManager = new Orc.Util.AspDotNet.DefaultUrlManager(count, PageSize, "page", "count");
- }
- }
- protected void BindData()
- {
- Int32 page = PageUtil.GetCurrentPage(this.Page);
- if (page < 1) page = 1;
- BindData(page);
- }
- protected abstract void BindData(Int32 page);
- }
然后是具體的控件:
- Controls_NewsList
- public partial class Controls_NewsList : BaseListControl
- {
- private Int32 m_categoryId = -1;
- public Int32 CategoryId
- {
- get { return m_categoryId; }
- set { m_categoryId = value; }
- }
- protected override Orc.Util.AspDotNet.PagerControl GetPager()
- {
- return this.pager;
- }
- protected override int GetResultCount()
- {
- return NewsService.SelectCount(null, this.Mark, this.Mode, this.CategoryId, this.InDays);
- }
- protected override void BindData(int page)
- {
- this.rpList.DataSource = NewsService.SelectData
- (null, this.Mark, this.Mode, this.CategoryId, this.InDays, this.OrderBy, this.PageSize, page);
- this.rpList.DataBind();
- }
- }
這樣一封裝就超級好用,可以顯示pager也可以不顯示,可以用在分頁里,也可以用在欄目的首頁,且代碼量幾乎降低到最低,而性能幾乎提高到最高,還可以根據(jù)各種參數(shù)進(jìn)行緩存。
四、抱怨
盡管使用了這些技巧,Web開發(fā)還是太累。一個(gè)月坐下來還沒做其它項(xiàng)目三五天賺的錢多,并且技術(shù)支持比其它類型項(xiàng)目難得多。做一個(gè)Web項(xiàng)目后悔一次,今后,盡量不做了,除非價(jià)格很高或自己用。