[修煉營 ASP.NET]網頁檢視物件資訊
前言
通常程式開發完畢到要進行SIT或UAT的階段時,
往往會出現一些「非crush」的錯誤,例如Session的轉換、流程的轉換出了問題,
或是某些物件的值、資料出現了與預期不符合的情況。
這個時候通常前方戰線的測試小組會發問題單給開發團隊來修改bug,
但是在大後方的開發團隊卻缺乏相關的資訊來還原現場,
很常出現家裡是好的,前線戰場的結果卻是錯的。
Solution
以前言裡面的情況來說,原因跟可以做的應對措施有很多,
通常都是藉由log來記錄與判斷到底是哪邊出了錯。
如果只是單純的Session或Request值,則只需要將aspx上的Trace設定為True即可。
但是若Session裡存放的是「物件」,或是不希望讓線上的User發現正在debug這支程式,
那就不適合用Trace,只能透過log或是隱藏的區塊來得知物件資訊。
這邊要介紹的,就是期望給開發人員可以埋入想要查詢的物件資訊,透過組合鍵,讓測試小組可以把相關資訊直接呈現在網頁上,
並將這些資訊附加於問題單上,降低開發團隊修正bug的難度。
在一般開發過程中,也由於之前系統使用Spring,往往都是透過Interface在操作物件,
在Visual Studio裡面移至定義只能移到interface,透過這個方式,我們也可以檢視,到底最後從Spring config上讀取實際注入的物件為何。
有了namespace,要找到該物件的類別檔案就方便很多了。
Play it
基本想法,就是在MasterPage埋一個容器,在Debug mode的時候,在網頁上按下組合鍵,可以顯示出開發人員想要得知的資訊。
而開發人員在自己撰寫的頁面上,只需要一行,就可以將要顯示的物件資訊記錄於頁面中。
使用到的技巧,只有幾個小撇步:
- client端的顯示、隱藏與動態註冊javascript偵測組合鍵
- 判斷webconfig裡面debug=true的判斷式
- 動態新增controls
- System.Reflection將整個物件屬性攤平
- BasePage上寫共用的method供頁面使用
接著我們一步接著一步,快樂地把它做出來吧。
首先,先在我們的MasterPage上加上一個要顯示資訊的區塊。
這邊container的ID我就先定死了,如果有需要修正,請搭配js一起修正。
還有就是因為要註冊的事件是body的onkeydown,所以我們需要把body加上id以及runat=”server”的標記。
.master
<body id="body" runat="server">
</body>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<div id="91" style="display: none;">
<asp:Panel ID="FP_Status" runat="server" ScrollBars="Auto">
</asp:Panel>
</div>
</ContentTemplate>
</asp:UpdatePanel>
接著在.master.cs的Page_PreRender事件,加上我們要註冊的javascript,當DEBUG為true時才作這件事。
在我的例子裡,javascript偵測的組合鍵是「ctrl+alt+g」
.master.cs
protected void Page_PreRender(object sender, EventArgs e)
{
#if(DEBUG)
string js = @"
function OpenprojectInfo() {
document.getElementById('ctl00_FP_Status').style.display = '';
document.getElementById('91').style.display = '';
var strHtml = document.getElementById('91').outerHTML;
document.getElementById('91').style.display = 'none';
faux = window.open('', 'OpenprojectInfo', 'dependent,resizable,top=0,left=0,width=0px,height=0px,z-look=no,scrollbars=yes');
var fd = faux.document;
fd.open();
fd.write('<html>');
fd.write('<body onLoad=""this.focus()"" >');
fd.write(strHtml);
fd.write('</body></html>');
fd.close();
return false;
}
function LockNewWindow() {
if ((event.ctrlKey) && (event.altKey) && (event.keyCode == 71)) {
OpenprojectInfo();
};
};";
ScriptManager.RegisterClientScriptBlock(this.Page, this.Page.GetType(), "showSession", js, true);
this.body.Attributes.Add("onkeydown", "LockNewWindow();");
#endif
}
接著就是最麻煩的動態繪製表格的部分,
一點都不難,只是真的太囉唆了。
這邊我們設計在BasePage裡的一個Protected的void,讓繼承BasePage的頁面都可以使用這個方法,
將我們動態繪製表格html的部分加入至MasterPage的容器裡。
由於之前只想到要呈現Session裡物件的屬性資訊,所以名字就命成WriteSession,要用記得要改漂亮一點的名稱。
為了讓開發人員方便區隔同樣的物件型別,不同的instance,所以我加上了第二個參數,
可以讓開發人員自行命名這個物件要呈現的資訊名稱。
BasePage.cs
protected void WriteSession( object targetObj,string mylogName)
{
#if(DEBUG)
StringBuilder strHtml = new StringBuilder();
strHtml.Append("<table border=\"1\" style=\"width:100%;\">");
Type type = targetObj.GetType();
PropertyInfo[] properties = type.GetProperties();
StringBuilder ColName = new StringBuilder();
strHtml.Append("<tr>");
strHtml.Append("<td colspan=\"3\" align=\"center\" style=\"background-color:#E2C822\" > " + type.ToString());
strHtml.Append("</td>");
strHtml.Append("</tr>");
strHtml.Append("<tr>");
strHtml.Append("<td colspan=\"3\" align=\"center\"> ");
strHtml.Append("</tr>");
foreach (PropertyInfo property in properties)
{
strHtml.Append("<tr>");
strHtml.Append("<td style=\"width:30%\">");
strHtml.Append(mylogName);
strHtml.Append("</td>");
strHtml.Append("<td style=\"color:blue;width:30%\" >");
strHtml.Append(property.Name);
strHtml.Append("</td>");
strHtml.Append("<td style=\"width:40%\" style=\"table-layout: fixed;WORD-BREAK: break-all; WORD-WRAP: break-word\">");
if (targetObj != null && property.GetValue(targetObj, null) != null)
{ strHtml.Append(property.GetValue(targetObj, null).ToString()); }
strHtml.Append("</td>");
strHtml.Append("</tr>");
}
strHtml.Append("</table>");
Label theResult = new Label();
theResult.Text = strHtml.ToString();
SetPanelValue("FP_Status", theResult);
#endif
}
這一段的重點在
Type type = targetObj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties){}
這三行,也就是將參數裡的物件,透過System.Reflection裡面的GetType()與type.GetProperties(),得到PropertyInfo[]。
另外就只是找MasterPage上的容器,加上我們想要給開發團隊的資訊,(這邊我的例子是呈現檔案實體路徑以及網頁路徑)
用Controls.add把我們剛剛繪製的html加進去Panel裡。
public void SetPanelValue(string vstrPanelName, System.Web.UI.Control vobjControl)
{
Panel objPanel = GetPanelObject(vstrPanelName);
if (objPanel != null)
{
if (objPanel.Controls.Count==1)
{
StringBuilder strHtml = new StringBuilder();
strHtml.Append("<ul><li>實體路徑:" + Request.PhysicalPath + "</li><li>網頁路徑:" + Request.Path + "</li></ul>");
Label header = new Label();
header.Text = strHtml.ToString();
objPanel.Controls.Add(header);
}
objPanel.Controls.Add(vobjControl);
}
}
private Panel GetPanelObject(string vstrPanelName)
{
if ((this.Master != null))
{
if (typeof(Panel).IsInstanceOfType(this.Page.Master.FindControl(vstrPanelName)))
{
return (Panel)this.Page.Master.FindControl(vstrPanelName);
}
}
return null;
}
就這樣,大功告成了。
接下來就是我們的頁面要怎麼使用,
也相當簡單,
- 要套用我們的MasterPage
- 要繼承我們的BasePage
使用方式,這邊舉個例子,在Page_Load()裡面讀取Session物件,
(請注意我這邊的範例是偷吃步的,正常應該透過Get Session跟轉型才是我們存放在Session裡的物件)
也順便拿我們在上一篇IoC裡面的myDevice來當例子。
test.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
SessionCustomer customer = new SessionCustomer();
customer.ID = "91";
customer.GroupID = "655";
SessionCustomer customer2 = new SessionCustomer();
customer2.ID = "ou";
customer2.GroupID = "BigO";
SessionCase caseSession = new SessionCase();
caseSession.CaseSn = "79979";
caseSession.CaseName = "小龍女";
SessionProcess.SetSession(SessionProcess.enuSessionType.Customer, customer);
SessionProcess.SetSession(SessionProcess.enuSessionType.Case, caseSession);
myDevice = (Core.Domain.Interface.IDevice)Core.WebUtility.Repository.Domain("Device");
myDevice.ID = "91";
myDevice.Style = "帥哥";
#if(DEBUG)
WriteSession(myDevice, "我的裝置");
WriteSession(customer,"我的人客");
WriteSession(customer2,"我的第二個人客");
WriteSession(caseSession,"我的Case");
#endif
}
}
要使用就這麼簡單一行
WriteSession(物件, “給人看的辨別資訊”);
接著我們來看畫面:
這樣子其實算是埋後門的方式,被User知道的話,可能會跳腳,
所以我們才會加上判斷Debug的條件。
只要改一個地方,這些功能就都會消失,畫面也不會有問題。
web.config
<compilation debug="true">
只要將debug的true改為false即可。(通常release後都是設定為false)
[補充]Control也能丟進去唷,效果就像網頁版的監看式
[註2]2009/10/9,偵測Debug是否為True的部分,可透過蹂躪大這篇文章:[C#]Effective C# 條款四: 使用ConditionalAttribute替代#if條件編譯
加上參考using System.Diagnostics;且在method上加[Conditional("DEBUG")],即可把#if(DEBUG)移掉,效率也會比較好。
blog 與課程更新內容,請前往新站位置:http://tdd.best/