top
Loading...
NET移植案例學習:建造Web站點

本文要求你熟悉Visual Studio 6.0,ASP和IIS

難度:2

摘要:當收集客戶對Visual Studio .NET beta版反饋的網站需要升級的時候,Visual Studio開發小組發現這是一個使用.NET技術來開發一個真正的網站的好機會。本文描述了移植beta.visualstudio.net站點的過程,說明了如何使用ASP .NET和.NET框架將原來用Visual Basic 6.0和ASP開發的,發布在Microsoft Internet Information Services (IIS) 5.0上的組件移植到.NET平臺下。還將介紹技術的選擇和檢查有效性、安全、認證等問題,并對現存的和新的用JScript 及ASP .NET開發的用作移植的組件作必要的解釋。

Visual Studio開發小組設計了從測試站點得到用戶反饋的機制,這是Visual Studio .NET beta版的一個部分。為此開發組在開發環境中增加了一個LAME按鈕,它能幫助用戶提交對這個產品的任何意見,包括對錯誤信息描述和菜單設計的意見等(參見圖1)。開發小組還設計了跟蹤用戶如何使用產品的機制。產生的日志文件能幫助開發組進一步確定用戶的需求。開發組用http://beta.visualstudio.net這個站點來收集這些信息和用戶發現的漏洞,并向用戶發布升級信息。



圖1 LAME按鈕

這個Web站點的以前使用基于Microsoft Internet Information Services(IIS)5.0的Visual Basic? 6.0和ASP開發的。隨著.NET產品的不斷成熟,開發組希望通過在一個真實的開發環境中使用他們以獲得第一手的體驗,所以決定將這個站點移植到Visual Studio .NET和Microsoft .NET框架下。

通過自己的親身體驗,開發組對產品有了進一步的認識,為其他開發者充分利用.NET平臺和Visual Studio .NET提供了很好的范例,還了解了哪些開發技術在Visual Studio .NET中仍然可以使用。

在這篇文章中,我將介紹將這個Web站點移植到.NET平臺時使用的一些技術,得到的教訓,并提供一些技巧來幫助你將應用程序移植到.NET平臺。

原先的站點設計

這個跟蹤站點由三個獨立的Web站點組成。普通用戶可以通過公眾站點登陸、報告產品漏洞、上傳日志文件、查看升級信息和已知的問題。Microsoft的員工可以通過內部網訪問鏡像站點無須通過公司的防火墻。Beta版的程序管理員可以通過管理站點得到測試網站的清單,管理站點的內容,配置站點其它的信息。

這個Web站點原先是用Visual Basic 6.0,ASP, Windows 2000和SQL Server? 7.0建立起來的。那時候,Visual Studio .NET和.NET框架是作為技術預覽被發布的,用它來建造這個站的模塊還顯得不成熟。因此,我們用了Visual Studio和Windows2000的技術來建造這個站點的。

這個站點的體系結構符合Windows DNA 2000應用程序設計模型。我們用Active Server Pages 3.0開發了客戶端的UI,并發布在IIS5.0上。這個程序的邏輯和數據訪問代碼在一個用Visual Basic 6.0寫出的ActiveX DLL中,并注冊到了COM+中。Visual Basic使用ActiveX Data Objects (ADO)來訪問SQL Server 7.0數據庫。圖2 說明了這個站點基本的體系結構。



圖2 測試站點的體系結構

當開發組考慮如何將這個站點移植到Visual Studio .NET Beta 1的時候,他們決定只修改外部的客戶站點(我將在下部分解釋為什么這么做)而不修改管理站點。升級外部站點能實現他們獲得第一手資料的愿望,并為客戶提供了一個如何在.NET下開發軟件的范例。

移植方法的選擇

將站點移植到Visual Basic .NET和.NET框架的第一步是看看有哪些方法可供選擇。現在有三種方法可以使用:

· 將站點和Visual Basic 6.0的組件移植到ASP .NET和Visual Basic .NET

· 將站點移植到ASP .NET,再用COM+ interoperability與現存的Visual Basic 6.0組件通訊

· 不改變現存的站點,而通過增加用ASP .NET和Visual Basic .NET寫的新的功能模塊來擴展站點的功能

在開始開發之前,開發小組確定了利用.NET的哪些功能模塊來替代網站中復雜的,且有時會有問題的代碼,并增加一些新的功能。具體的講,他們希望按下面的要求重建網站:

· 用ASP .NET認證來替代原來的用戶安全機制

· 用ASP .NET Web Form確認控件來替代客戶端用于報告產品漏洞和描述缺點的確認邏輯

· 用ASP .NET Web Services將Microsoft其它的網站溶入beta版產品漏洞報告體系中

· 利用.NET框架的本地化功能建造一個可以很容易實現本地化的Web站點

· 利用ASP .NET向用戶提供個性化的菜單和橫幅圖片

為了充分利用這些特點,開發組決定將ASP頁面移植成ASP .NET頁面。他們將不移植現存的Visual Basic 6.0的ActiveX組件,而是創建新的Visual Basic .NET組件來實現Web Service和本地化。

現在讓我們看看為了完成移植,對這個網站到底做了哪些修改。

結合ASP .NET認證功能

移植的第一步就是用ASP .NET中基于cookie的認證機制來替換原來的客戶安全機制。這種安全認證機制首先出現在PDC技術預覽中,并在Visual Studio .NET Beta 1中得到了發展。它的目標是確定誰在訪問網站,而不是阻止用戶訪問。因此,開發組修改了成員資格系統,幫助用戶注冊到Web站點,并且在以后的Beta版產品中可以繼續使用。

識別每個訪問者的目的是跟蹤他們報告的漏洞和缺點,并與他們進行必要的交流,以徹底解決問題。除了把用戶的反饋送到特定的測試站點,用戶還可以定制這個站點,以滿足自己的需要,幫助客戶將注意力集中在他們需要的信息上。把用戶和他們感興趣的內容聯系起來能幫助站點管理員了解用戶對什么問題最感興趣。大多數測試站點包括了Visual Studio .NET和.NET框架各個方面的內容,允許用戶訪問站點上所有的文檔,但某些用于特定方面(比如Visual Studio .NET IDE shell整和)的測試站點利用過濾器向客戶只提供他們感興趣的文檔。

原先使用的認證方式使用一個ASP服務器端文件和一個Visual Basic組件所提供的方法,驗證來訪者所提供的用戶ID和密碼是否是數據庫的成員。這個文件提供了可重用代碼來完成安全檢查,但這就意味著這個文件需要被包含在每一頁的開頭,才能保證這一頁不會被未經過認證的用戶打開。在每一頁包含這個文件給管理員配置不需要安全保護的頁帶來了麻煩。

在ASP .NET中實現認證是很容易的,因為基于cookie的認證通過將站點的文檔存放在某一個特定的文件夾實現了對文檔的保護。當用戶企圖訪問這個受保護的文檔時,.NET框架將自動判斷用戶是否經過了認證。如果用戶未被認證,.NET框架會把這個未經認證的請求重定向到某一個特定的HTML表單,讓用戶輸入認證信息,并提交這個表單。如果用戶得到了認證,.NET框架會產生一個可以辨別用戶的cookie,并重定向到原先的請求頁面。.NET框架還提供了一些類來幫助我們與認證過程交互和訪問保存在cookie中的認證信息。

我們可以用Web站點的config.web文件來配置ASP .NET的認證體系。這個配置文件包含了一個用于指定認證方式的塊、該塊指明了HTML登陸表單的URL和密碼的格式。圖3是一個設置基于cookie的認證的config.web文件。

Web站點上原來的那種安全認證方式為每一個訪問者唯一確立了一個對話ID。因為已經生成了這個ID,所以我們在移植認證過程不要改動現存的代碼。ASP .NET認證機制將把未經認證的用戶重定向到登陸頁,讓用戶提交信用證。一旦提交了登陸頁,用戶ID和密碼將被確認,還將產生一個會話ID。

實現ASP .NET認證體系只要修改原來的登陸頁面的兩個地方。我們沒有向客戶的cookie寫入會話的關鍵字,而是使用了ASP .NET認證cookie,將它的值設為會話的關鍵字。然后通過CookieAuthentication 類的RedirectFromLoginPage方法將用戶重新引導到原先的請求頁。在接下來的請求中,將通過.NET框架的HttpContext.User類來訪問會話ID。圖4顯示的是修改后的登錄認證檢查。

除了提供了一種更安全、更容易實現的安全認證體制外,ASP .NET認證體制還可以區別對待認證過的和未經認證的內容。因為保護的范圍是由config.web文件中的目錄結構決定的,所以只要把內容移出受保護的文件夾就可以取消對內容的保護了。

為了讓認證機制能發揮作用,所有需要認證才可以訪問的文件的擴展名應改為.aspx(ASP.NET文件的擴展名)。正如你將在下面看到的,這是一個相對簡單的過程。

將ASP代碼移植到ASP .NET

開發組希望在移植這個Web站點的過程中,能盡可能地少寫代碼,以減少產生漏洞的可能。在這部分,我將向你說明將ASP頁面移植到ASP .NET最簡單的方法是什么。但使用這種方法不能充分利用ASP .NET的新特點,只能建立一個能在ASP .NET下運行的站點。

許多頁面需要做三四個標準的修改后才能正確運行。最常見的修改是要刪除Set關鍵字。.NET框架和底層的common language runtime (CLR)把任何東西都當作對象處理,所以刪除了Set這個關鍵字。下面這段代碼取自原先的站點:

'create the feedback object
set oFeedback = Server.CreateObject("BetaSiteMgr.Feedback")

應該改為 :

'create the feedback object
oFeedback = Server.CreateObject("BetaSiteMgr.Feedback")

另一個常見的改動是,在ASP頁中引用一個對象時,要明確指定這個對象的默認屬性。CLR和ASP .NET不再支持不傳遞參數的默認屬性。不幸的是,盡管明確指定屬性的值可以增加代碼的可讀性和可持續性,許多開發者更愿意利用默認屬性的特點而不打出值。在這個Web站點中,當需要使用ADO記錄時,默認屬性經常被使用。原來的ASP代碼像這樣:

'set rs fields


rsLameError("BetaID") = Request.Form("betaid")
rsLameError("UserDescription") = Request.Form("bugDescription")
rsLameError("SeverityID") = Request.Form("severity")
應改為:
'set rs fields

rsLameError("BetaID").Value=Request.Form("betaid")
rsLameError("UserDescription").Value=Request.Form("bugDescription")
rsLameError("SeverityID").Value=Request.Form("severity")

第三種常見的修改是改變類型轉換函數的使用方式。再ASP .NET中,VBScript的類型轉換函數被加到了.NET框架中。這個變化要求下面的ASP代碼

adoRS.fields("AreaID") = clng(oUploadManager.Form("cboArea"))
adoRS.fields("SubAreaID") = clng(oUploadManager.Form("cboSubArea"))

轉變為:

adoRS.fields("AreaID").Value =
oUploadManager.Form("cboArea").ToString().ToInt()
adoRS.fields("SubAreaID").Value =
oUploadManager.Form("cboSubArea").ToString().ToInt()

最后一個常見的修改是將調用Response.Write方法的格式改為ASP .NET中的方法調用格式。在VBScript,調用不返回值的過程時,不需要用小括號把參數括起來。但在ASP .NET中,所有傳遞到方法中的參數都要用括號括起來。在原來的站點中,在很多地方都調用了Response對象的Write方法,而且都沒加小括號。為了將站點移植到ASP .NET,有必要將下面這段代碼:

<% Response.Write "Thank You!" %>

改為:

<% Response.Write("Thank You!") %>

在對原來的ASP頁面做了這些修改之后,這個Web站點就可以運行在ASP .NET下了。移植工作只要用兩周就可以完成了。為了知道什么地方需要作修改,開發組應用Visual Studio .NET IDE新建了一個新的Visual Basic Web應用程序,將現存的ASP文件的擴展名都改為.aspx,并把它們加到工程中。一旦頁面在IDE中顯示了出來,Visual Studio .NET就高亮顯示出語法,列出要作修改的地方,幫助我們這樣發現編譯期錯誤。

LAME報告和驗證控件

在移植過程中,開發組決定修改數據輸入頁以充分發揮ASP .NET檢驗控件的優勢,并降低現存代碼的復雜度。當用戶想提交一個LAME報告時,他們在點擊錯誤對話框(幫助文檔的反饋鏈接)上的LAME按鈕,或者是工具欄上的Feedback按鈕之后,在Visual Studio .NET IDE中會見到一個頁面。為了將檢驗控件整合到LAME輸入頁,有必要重寫這些頁,并充分利用ASP .NET Web Form技術。

Web Form檢驗控件提供了一套獨立于瀏覽器的校驗函數,而不需要你在客戶應用程序中用腳本語言寫代碼。這個控件可以在低版本的瀏覽器中使用(包括Microsoft Internet Explorer 4.0以前的版本和Netscape 5.0以前的版本),但需要利用服務器來檢查控件的值并返回結果。當發現高版本的瀏覽器(Interne Explorer 4.0及其以后的版本和Netscape 5.0及其以后的版本)時,驗證的主要步驟是在向服務器發送數據之前,在客戶端完成的。在使用該版本瀏覽器時,如果被提交到服務器頁面企圖欺騙或繞過驗證規則,服務器端的驗證仍將進行。

Figure 5.

原來的LAME入口頁中的驗證邏輯的目標很簡單:要求用戶在提交表單之前已經在某些字段上填了值。盡管目標很簡單,但它需要類似于圖5中的Jscript代碼的客戶端腳本代碼。

除了需要在客戶端用腳本語言進行驗證,還要在服務器上增加驗證邏輯,以防止用戶繞過客戶端的驗證并提交無效數據。使用了ASP .NET驗證控件后,可以用Required,Compare和 RegularExpressionValidator等控件替換這些驗證邏輯。這樣不僅可以從頁面中刪掉很多Jscript代碼,而且可以利用RegularExpressionValidator控件來加強驗證。但當發現了高版本的瀏覽器時,仍將向客戶端發送Jscript代碼,這樣就可以在提交到服務器進行驗證前進行客戶端驗證,。不同處在于開發組不需要編寫Jscript代碼,驗證控件會自動生成合適的代碼。

當提交漏洞報告或一般的LAME報告時,需要同時提交所使用產品的版本。在大多數情況下,用戶可以從下拉列表中選出版本號,而唯一的驗證就是檢查用戶是否選擇了一個版本號(參見圖6)。但在某些情況下,比如未列出所用的版本時,用戶需要手動輸入版本號。在這種情況下,可以用兩種格式來輸入版本號,在提交到服務器前將檢查格式是否正確。用來完成這種驗證的Jscript代碼可能很復雜,但如果使用RegularExpressionValidator控件,只要定義正確的表達規則就可以進行驗證了。下面這段代碼使用了RegularExpressionValidator控件,并設置了可以完成這種檢查的屬性。

<asp:RegularExpressionValidator id=txtBuildValueValidator

runat="server" display="dynamic" controlToValidate="txtBuild"

errorMessage="Also, please enter the Build Version in
the following format: NN.NN.NN.NNNN "

validationExpression="d{2}.d{2}.d{2}.d{4}|d{4}">##

</asp:RegularExpressionValidator>

用于報告漏洞的Web Service

這次移植還要增加報告漏洞的輸入容量,并用Web Service來實現漏洞報告。為了有效的發布有關Visual Studio .NET和.NET框架的信息,Microsoft建立了許多包含了例子和信息的Web站點。這些站點需要一種機制,允許用戶通過其它的Web站點報告漏洞,并將這些信息貯存在庫中。站點還要求能實現離線報告漏洞,這樣用戶可以一次將許多漏洞發送到站點。要想實現這些目標,我們應該使用Web Service來完成報告漏洞的任務。

為了便于分類管理漏洞報告,而且能讓合適的開發組看到報告,在一份漏洞報告中還需要提交許多其它數據(比如操作系統、語言、產品、地區等)。為了能讓客戶端應用程序提供豐富、有用的接口,有必要向客戶端應用程序提供這些信息。為了能適用于更多的程序,開發組決定使用XML來傳輸數據。

為了簡化報告漏洞服務的使用,開發組開發了多種接口,并實現了用多種方法保存報告。一個接口要求用戶提供與漏洞報告數據有關的ID值,另一個接口允許客戶端應用程序用字符串提交其它數據。如果采用后一種方式,漏洞報告服務將把字符串轉變為相關的ID,并通過漏洞報告組件完成驗證并保存。

原來的用Visual Basic 6.0開發的漏洞報告組件使用ADO與客戶端程序進行數據交換。為了實現用XML傳輸數據,我們可以使用ADO記錄的adPersistXML格式將記錄中的數據轉變為XML文檔。最后還要將得到的XML漏洞報告轉變為ADO記錄,以便用Visual Basic 6.0的組件進行處理。

開發小組決定在Visual Basic .NET中重新編寫報漏洞的類,這樣可以充分利用ADO.NET中基于XML的dataset。這樣做使得他們一方面可以方便地將數據以XML的形式提供給客戶端的應用程序,同時,還可以使用Visual Basic .NET組件中簡單易用的對象接口與數據進行交互。

一般說來,編寫轉換數據格式的代碼是一種更為安全的選擇。但考慮到我們遷移工程的主要目的是為了體驗一下新的技術,采取這樣的轉換方法還是有一定的實際意義的。

移植站點的設計

在這一點上,開發組實現了開始這個項目時所定的目標。現存的客戶站點被移植到了ASP .NET下,在這個過程中,還成功整合了.NET框架提供的新功能。另外,利用Visual Basic .NET開發出了一些新的功能模塊,增強并擴展了這個站點的功能而并沒有站點的大部分代碼。圖7說明了這個站點的體系結構是如何演變成現在的樣子的。



圖7 新的體系結構

新的體系結構和Web站點增強了Visual Studio .NET和.NET框架開發組與測試站點間的聯系。新的漏洞報告系統融入了現存的漏洞報告體系結構中,并能送到恰當的開發組手中。另外,移植站點為開發組提供了獲得第一手資料的機會。

得到的教訓

在遷移到Visual Studio .NET Beta的過程中,我們發現一些在Visual Basic 6.0和ASP中使用的開發技術,可以幫助你更容易的將程序實現移植到.NET。這種技術代表了良好編程風格,無論你是否移植到Visual Studio .NET,都能幫助你維護你的應用程序。

為了使你以后能更方便的實現遷移,請記住以下兩點:

1. 不要依靠默認的對象屬性。堅持在你的應用程序中顯式地引用默認的屬性。

2. 在調用方法時,應該在參數外加上圓括號。

ASP.NET和Visual Basic .NET可以和COM+組件實現通信的能力,使得移植應用程序成了一件簡易完成的工作。這種互操作性可以使你大大的縮減在升級應用程序到.NET平臺時所需修改的代碼量。你可以以現有代碼為基礎,通過在Visual Basic .NET和ASP .NET中創建新的組件來實現對應用程序的遷移和功能擴充。

如果你想了解背景知識,請閱讀:

Visual Studio .NET站點

Microsoft .NET開發站點

ASP .NET應用程序

Jay Schmelzer是Clarity Consulting Inc.的合作伙伴。他負責檢查設計、開發和實現大規模系統的開發小組的工作。他多次撰寫了技術文檔,并在會議上發言。

圖3 Config.web文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<!-- SECURITY
This section sets the security policies of the application.
Possible modes are "Windows", "Cookie",
"Passport" and "None"
-->

<!-- Use Cookie Authentication for external users -->


<security>
<authentication mode="Cookie">
<cookie cookie=".PRODAUTH" loginurl="https://
beta.visualstudio.net/Login.aspx"
decryptionkey="autogenerate">

<credentials passwordformat="Clear" />
</cookie>
</authentication>
<authorization>
<allow users="*" />
</authorization>
</security>
</configuration>

圖4 認證代碼

Public Sub cmdSubmit_Click(ByVal sender As Object, ByVal e As _
System.EventArgs)
Dim sUserID As String
Dim sPassword As String
Dim sAuthCookie As String

'External user, take the values from the Login form
sUserid = system.Convert.ToString(Me.txtUserID.Value)
sPassword = system.Convert.ToString(Me.txtPassword.Value)

adoRS = oBetaUser.LoginEx(sUserID, sPassword)

If adoRS.recordcount = 1 Then
'login success - get a session
adoRS = osSession.GetNewSession _
(adoRS("BetaSiteID").Value.ToString().ToInt32())

If Not adoRS Is Nothing Then
If adoRS.Recordcount > 0 Then
adoRS.movefirst()

'Set the authentication cookie using the SID
sAuthCookie = adoRS("SID").Value.ToString()

'use ASP+ authentication to authenticate the user,
If Instr(CookieAuthentication.GetRedirectUrl _
(sAuthCookie, True), "default.aspx") > 0 Then
CookieAuthentication.SetAuthCookie(sAuthCookie, True)
Response.Redirect(system.Convert.ToString( _
oSiteUser.GlobalPath) & "Home.aspx")
Else
CookieAuthentication.RedirectFromLoginPage( _
sAuthCookie, True)
End If
Else
'login failed
Response.Redirect("LoginFailed.aspx")
End If
Else
'login failed
Response.Redirect("LoginFailed.aspx")
End If
Else
'login failed
Response.Redirect("LoginFailed.aspx")
End If
End Sub

圖5 客戶端的驗證邏輯

FormSubmitTest(problemTitle,bugDescription,buildVersion,cboProduct,
appLanguage,cboArea, cboSubArea,severity,build)
{
var iG;
var renl = "" + "";
iG = 0;
ResetArrowSize();
if (problemTitle.value == "" ){
document.images.item("ArrowProbTitle").width=8
iG++
}
if (buildVersion.value == -1){
document.images.item("ArrowBuildVersion").width=8
iG++
}
if (buildVersion.value == ""){
document.images.item("ArrowBuildVersion").width=8
iG++
}
if (appLanguage.value == -1){
document.images.item("ArrowAppLanguage").width=8
iG++
}
if (cboProduct.value == ""){
document.images.item("ArrowProductName").width=8
iG++
}
if (cboArea.value == ""){
document.images.item("ArrowProblemArea").width=8
iG++
}
if (cboSubArea.value == ""){
document.images.item("ArrowProblemSubArea").width=8
iG++
}
if (severity.value == -1){
document.images.item("ArrowSeverity").width=8
iG++
}
if (bugDescription.value == "Problem Description:" + renl + renl +
"Steps to reproduce:" + renl + renl + "Expected behavior:"
+ renl + renl + "Actual behavior:"+ renl + renl + renl){
document.images.item("ArrowDescription").width=8
iG++
}
if (bugDescription.value == ""){
document.images.item("ArrowDescription").width=8
iG++
}
if (iG != 0){
alert("You are missing one or more of the required items
(indicated by the red arrow) needed to submit this form.")
return false;
}
else{
if (document.form1.bugDescription.value == "")
{
// don't submit
alert("Please enter a problem description.")
return false;
}
else{
if (document.form1.bugDescription.value == "Problem
Description:
Steps to reproduce: Expected behavior: Actual
behavior:")
{
// don't submit
alert("Please enter a problem description.")
return false;
}
else{
var buildval = build.value;
var buildlen = buildval.length;
file://alert(buildlen);
if (buildlen <= 0)
{
var typ = buildVersion.type;
if (typ == "text") file://build is a text box
{
var val = buildVersion.value;
var len = val.length;
if ((isNaN(val)) || (len != 4))
{
// don't submit
alert("Please enter 'Build' using the
format 'NNNN'.")
return false;
}
else
{
// concatenate the build number with
"07.00.00."
buildVersion.value = "07.00.00." +
buildVersion.value
// submit
document.form1.method = "POST"
document.form1.action = "lamegeneral.asp"
return true;
}
}
else
{
// submit
document.form1.method = "POST"
document.form1.action = "lamegeneral.asp"
return true;
}
}
}
}
}
}

圖6 在報告漏洞時選擇產品的版本



>
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗