目的:
        實現用masterpage中的.cs文件 代替項目中的pagebase。
動機:        
        寫這篇文章的動機,來自于一次項目重構。在.net framwork 2.0的b/s架構項目中同時采用pagebase和masterpage技術,發現每次訪問頁面,頁面同時訪問pagebase和masterpage,不僅造成性能降低,甚至有可能給日后的項目功能擴充和調整帶來邏輯錯誤隱患。
技術環節:
        pagebase:.net framework 1.1 中經常使用的一種封裝多個頁面相同功能的技術。pagebase.cs類繼承自system.web.ui.page類,項目中的web頁面繼承自pagebase.cs類,通過重寫基類中的頁面初始化方法,實現調用pagebase中的業務功能,例如:url參數驗證,保存訪問量等功能(具體實現方式參見微軟官方例子duwamishi)。
        masterpage:.net framework 2.0 中新特性,物理上包括兩個文件,分別是:.master文件(html標記),.cs文件(c#代碼)。.master文件實現顯示層繪制,.cs文件實現具體功能。繼承自masterpage的web頁面可以繼承masterpage中的顯示層內容。繪制通用的頁頭頁腳,定制統一的布局,masterpage是不錯的選擇。
模擬需求:
       用masterpage技術,代替pagebase,實現地址欄參數驗證。
簡單的做個解釋吧,數據庫中login表信息如下圖:             
登錄系統之后,url地址欄中帶有參數,如下:
http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1001
此時用戶手動修改url地址欄中參數為:
http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1002
被視為非法操作,系統將自動跳轉回登錄頁面。
第一次代碼迭代:
1.參照傳統pagebase方法:
        傳統的page做法為:
public class pagebase : system.web.ui.page
{    
    public pagebase()
    {
    }
    /**//// <summary>
    /// 入口方法
    /// </summary>
    protected void initialize()
    {
        // 插入通用業務邏輯
     }
}
        web頁面:
public partial class testpage : pagebase
{
    // 傳統的調用pagebase的方法     
    /**///// <summary>
    /// 重寫基類onpreinit() 方法,調用通用驗證方法
    /// </summary>
    /// <param name="e"></param>
    protected override void oninit(eventargs e)
    {
        base.initialize();
    }
}
參照其做法,將pagebase中的代碼移入masterpage中:
masterpage.cs:
public partial class mymasterpage : system.web.ui.masterpage
{
    protected void page_load(object sender, eventargs e)
    {
        if (!ispostback)
        {
            // 調用驗證方法
            initialize();
        }
    }
}
將web頁面中的代碼修改為: 
public partial class testpage : system.web.ui.page
{    
    // 仿照pagebase方法,調用master中的方法  
    /**//// <summary>
    /// 重寫基類onpreinit() 方法,調用通用驗證方法
    /// </summary>
    /// <param name="e"></param>
    protected override void oninit(eventargs e)
    {        
        // 獲得母板頁引用
        mymasterpage mymasterpage = (mymasterpage)this.master;
        // 調用母板頁中通用驗證方法
        if (!ispostback)
        {
            mymasterpage.initialize();
        }
    }
}將masterpage中的initialize()方法替換為實例中的,測試代碼:
        步驟1:用 用戶名zhangsan登錄系統,登錄成功,
                      頁面顯示 歡迎 zhangsan 登錄。
                      url地址顯示:
                      http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1001
        步驟2:手動修改url地址欄:如下:
                      http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1002
        頁面不會顯示 歡迎lisi登錄,而是跳轉回登錄頁面。
反思:雖然功能實現,但是存在不理想的環節:
        1. master中的被子類調用方法必須是public方法;
        2. 雖然不用修改web頁的繼承,但是依然要機械的復制粘貼重寫基類的oninit()方法。
為了消除這些懷味道,于是開始:
第二次代碼迭代:
修改masterpage.cs中的代碼: 
public partial class mymasterpage : system.web.ui.masterpage
{
    protected void page_load(object sender, eventargs e)
    {
        if (!ispostback)
        {
            // 調用驗證方法
            checklogin();
        }
    }
    /**//// <summary>
    /// 驗證訪問是否合法
    /// </summary>
    private void checklogin()
    {      
        // 如果 url中的編號 或 cookie中的編號
        if (string.isnullorempty(request.querystring["id"])
            || string.isnullorempty(cookieutil.readcookiebykey("id")))
        {
            response.redirect("login.aspx");
        }// 如果url中的編號 和 cookie中的編號 不匹配,返回登錄頁        
        else if (int.parse(request.querystring["id"]) != int.parse(cookieutil.readcookiebykey("id")))
        {
            response.redirect("login.aspx");
        }      
    }
}重構之后,web頁可以不進行任何修改,masterpage在自身的page_load()方法中自動調用驗證方法,而且將驗證方法設置為private,僅供masterpage自身調用,提高安全性。至此,代碼似乎比較理想了,測試:
        步驟一:用 用戶名 zhangsan登錄系統,
                        依然顯示用戶登錄頁面。
                        測試失敗。
用斷點跟蹤代碼,發現問題出現在masterpage.cs中的checklogin()方法中的代碼片段:
if (string.isnullorempty(request.querystring["id"])
            || string.isnullorempty(cookieutil.readcookiebykey("id")))
{
      response.redirect("login.aspx");
}
由于登錄頁繼承自masterpage,所以頁面加載時自動調用masterpage.cs中的驗證方法,而自身的參數又不滿足string.isnullorempty()方法,于是又跳回到登錄頁面,登錄頁面在再次在加載時調用基類中的驗證方法,于是形成死循環。
在pagebase技術中,web頁面可以有選擇的繼承自pagebase,而masterpage技術中,為了獲得一致的顯示層效果,web頁面對繼承masterpage的選擇性是非常底的,而且我們也不應該采用新建相同顯示,不帶有驗證代碼的masterpage,來給不需要繼承基類功能的web頁面來繼承,這種方式顯然不合理。為了解決這個問題,于是開始了
第三次迭代: 
引入配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<pages>
  <testpage>
    <page title="testpage" url="testpage.aspx" needvalidate="true"/>
    <page title="login" url="login.aspx" needvalidate="false"/>
  </testpage>
  <adminpages>
    <page title="page1" url="~/admin/page1.aspx" needvalidate="false"/>
    <page title="page2" url="~/admin/page2.aspx" needvalidate="false"/>
  </adminpages>
</pages>
從中可以看到,將需要驗證的頁面加以標識(needvalidate="true")。
創建xml數據訪問類:
public class xmldal
{
    private static string filepath = string.empty;
    static xmldal()
    {
        // 初始化配置文件路徑
        filepath = httpcontext.current.request.mappath("~/app_data/xml/" + "pages.xml");
    }
    /**//// <summary>
    /// 獲得需要驗證的頁面列表
    /// </summary>
    /// <returns>需要驗證的頁面列表</returns>
    public static ilist<string> getvalidatepages()
    {
        ilist<string> pages = new list<string>();
        // 如果指定配置文件存在
        if (system.io.file.exists(filepath))
        {            
            try
            {                
                xmldocument xmldoc = new xmldocument();                 
                xmldoc.load(filepath);
                // 獲取配置文件根節點
                xmlnode root = xmldoc.documentelement;
                string xpath = "/pages/testpage/page[@needvalidate='true']";
                xmlnodelist nodelist = root.selectnodes(xpath);
                // 便利節點集合
                foreach (xmlnode node in nodelist)
                {
                    pages.add(node.attributes["title"].value);
                }
            }
            catch (exception ex)
            {
                throw new exception(ex.message);
            }            
        }
        return pages;
    }
}
重構masterpage.cs中的代碼,加入isvalidateneeded(string url)方法,用于檢測當前頁面是否需要驗證,修改驗證方法:
public partial class mymasterpage : system.web.ui.masterpage
{
    protected void page_load(object sender, eventargs e)
    {
        if (!ispostback)
        {
            // 調用驗證方法
            checklogin();
        }
    }
    /**//// <summary>
    /// 驗證訪問是否合法
    /// </summary>
    private void checklogin()
    {
        // 判斷當前訪問頁面是否需要進行驗證
        if (isvalidateneeded(request.rawurl))
        {
            // 如果 url中的編號 或 cookie中的編號
            if (string.isnullorempty(request.querystring["id"])
                || string.isnullorempty(cookieutil.readcookiebykey("id")))
            {
                response.redirect("login.aspx");
            }// 如果url中的編號 和 cookie中的編號 不匹配,返回登錄頁        
            else if (int.parse(request.querystring["id"]) != int.parse(cookieutil.readcookiebykey("id")))
            {
                response.redirect("login.aspx");
            }
        }
    }
    /**//// <summary>
    /// 驗證當前頁是否需要驗證
    /// </summary>
    /// <param name="currentpage">當前頁面名稱</param>
    /// <returns>是否需要驗證狀態</returns>
    private bool isvalidateneeded(string url)
    {
        bool isneeded = false;
        // getvalidatepages() 方法返回需要驗證頁面列表
        ilist<string> pages = xmldal.getvalidatepages();
        ienumerator<string> ie = pages.getenumerator();
        while (ie.movenext())
        {
            // 如果當前頁面需要進行驗證
            if (url.contains(ie.current))
                // 返回需要驗證狀態
                return isneeded = true;
        }
        return isneeded;
    }
}
進行測試:
        步驟1:用 用戶名zhangsan登錄系統,登錄成功,
                      頁面顯示 歡迎 zhangsan 登錄。
                      url地址顯示:
                      http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1001
        步驟2:手動修改url地址欄:如下:
                      http://localhost:3730/masterpagebasedemo/testpage.aspx?id=1002
        頁面不會顯示 歡迎lisi登錄,而是跳轉回登錄頁面。
至此我的代碼迭代結束了。
代碼下載:
http://www.cnblogs.com/files/ayuan/masterpagebasedemo.rar
本人之前沒有寫技術文章的經驗,所以以上的文字難免晦澀,而且自身技術水平也有限,可能有些觀點不太成熟,歡迎各位朋友指正。
新聞熱點
疑難解答
圖片精選