目的:
實作用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)
{
// 呼叫驗證方法
檢查Login();
}
}
/**//// <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)
{
// 呼叫驗證方法
檢查Login();
}
}
/**//// <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登錄,而是跳轉回登錄頁面。
至此我的程式碼迭代結束了。
程式碼下載: