top
Loading...
ASP.NET2.0中的代碼隱藏和編譯
我還記得在 2003 年的 8 月坐在 Microsoft 公司的一個房間內傾聽 Scott Guthrie 和其他人(包括我的同事 Rob Howard,他也是專欄作家)介紹 ASP.NET 2.0 的大量新功能。他們演示了一個又一個功能,這些功能令我們非常吃驚,因為它們極大地簡化了 Web 開發,而且是以可插入和可擴展的方式實現的,因此在開發過程中能夠以任何所需級別進行更改。

后續測試版本中進行了大量更改,多數是以修改、錯誤修復和控件附加的形式進行的。但是,有一個功能(代碼隱藏模型)自從第一個預覽版以來已經進行了大量更改,這主要是為了響應客戶的反饋。現在即將發布之時,我想利用這個機會描述一下這個新的代碼隱藏模型、它的基本原理,以及 Web 開發人員將如何使用它。我也會介紹該模型的一些潛在的副作用以及如何在設計中解決它們。請注意,ASP.NET 2.0 運行時完全支持 1.x 模型,因此針對 1.x 編寫的應用程序可以在無需修改的情況下直接運行。

代碼隱藏

雖然該代碼隱藏模型在 2.0 中是不同的,但是它的語法已經進行了少量更改。實際上,該更改十分細微,如果您不仔細查看,甚至都無法注意到它。圖 1 顯示新的代碼隱藏語法。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="MsdnMag.Default" %>
Default.aspx.cs
namespace MsdnMag
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{}
}
}

該模型與以前的 1.x 模型有兩個區別 — 在 @ Page 指令中引入了 CodeFile 屬性,以及將代碼隱藏類聲明為部分類。當開始生成該頁時,您將注意到另一個區別 — 服務器端控件不再需要在代碼隱藏類中顯式聲明,但是您仍然能夠以編程方式完整地訪問它們。例如,圖 2 中的窗體有若干個在代碼隱藏文件中以編程方式使用的服務器端控件,但是您可以注意到,代碼隱藏類中缺少任何顯式控件聲明。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="MsdnMag.Default" %>
<!DOCTYPE html PUBLIC "..." "...">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Enter your name:
<asp:TextBox ID="_nameTextBox" runat="server" /><br />
<asp:Button ID="_enterButton" runat="server"
Text="Enter" OnClick="_enterButton_Click"/> <br />
<asp:Label ID="_messageLabel" runat="server" />
</div>
</form>
</body>
</html>

Default.aspx.cs
namespace MsdnMag
{
public partial class Default : System.Web.UI.Page
{
protected void _enterButton_Click(object sender, EventArgs e)
{
_messageLabel.Text = "Hello there " + _nameTextBox.Text + "!";
}
}
}

其中的原因與應用于代碼隱藏類的部分關鍵字有關。除了使用呈現該頁的方法將 .aspx 文件轉換為一個類定義(正如它已經做的一樣),ASP.NET 現在也為包含受保護控件成員變量聲明的代碼隱藏類生成一個同輩部分類。然后,您的類與該生成的類定義一起編譯,并用作針對 .aspx 文件生成的類的基類。結果是,您基本上以經常使用的方式編寫代碼隱藏類,但是您不再需要聲明(或讓服務器為您聲明)服務器端控件的成員變量聲明。這一直是 1.x 中一個不太穩定的關系,因為如果您無意間修改了一個控件聲明,使得它不再與該窗體上所聲明控件的 ID 匹配,就會突然停止工作。現在,成員變量以隱式方式聲明并始終是正確的。圖 3 顯示所涉及類集的一個示例。

Class for ASPX file generated by ASP.NET
namespace ASP
{
public class default_aspx : MsdnMag.Default
{
...
}
}
Sibling partial class generated by ASP.NET
namespace MsdnMag
{
public partial class Default : IRequiresSessionState
{
protected TextBox _nameTextBox;
protected Button _enterButton;
protected Label _messageLabel;
private HtmlForm form1;
...
}
}

Codebehind partial class that you write
namespace MsdnMag
{
public partial class Default : Page
{
void _enterButton_Click(object sender, EventArgs e)
{
_messageLabel.Text = "Hello there " + _nameTextBox.Text + "!";
}
}
}

請注意,該部分類模型僅當在 @ Page 指令中使用 CodeFile 關鍵字時使用。如果使用不帶 CodeFile(或者帶有 src 屬性)的 Inherits 關鍵字,ASP.NET 會使用 1.x 代碼隱藏類型并簡單地將類設置為 .aspx 文件的唯一基類。此外,如果您根本沒有代碼隱藏,則類生成與它在 1.x 中的操作將完全相同。由于 ASP.NET 2.0 向后與 1.x 兼容,因此現在有大量代碼隱藏選項供您使用。

Visual Studio 2005 將使用任何 Web 窗體新的部分類隱藏模型,而且如果您使用轉換向導,它也將很好地轉換 Visual Studio .NET 2003 項目以便使用新模型。因為 ASP.NET 2.0 的一些新功能依賴于它的原因,所以如果可能,最好將所有文件轉換為新代碼隱藏模型(如果使用 Visual Studio,那么轉換幾乎是唯一的選擇,因為 Visual Studio 2005 不會打開未轉換的 1.x 項目)。例如,對 Profile 屬性包的強類型訪問添加到 2.0 中代碼隱藏類的同輩部分類中,但是如果您使用 1.x 代碼隱藏模型,則該強類型訪問器直接添加到 .aspx 生成的類定義中,而且對于代碼隱藏類不可用。這也適用于強類型的母版頁和以前的頁訪問。

編譯

此時,您可能想知道,為什么 ASP.NET 小組非要使用這個新代碼隱藏模型來使用繼承。ASP.NET 除了將來自 .aspx 文件的方法呈現為部分類(然后這些類與簡化的代碼隱藏類合并)之外,還可以輕松生成所有控件變量聲明。這就是 Windows 窗體在 .NET Framework 2.0 中的工作方式。設計器生成的所有代碼被放置在同輩部分類(然后該類與您的應用程序邏輯合并)中,事件處理程序被放置在窗體驅動的單個類中,從而在無需借助于繼承的情況下,在計算機生成的代碼和開發人員代碼之間創建一個完全的分離。

ASP.NET 2.0 中代碼隱藏的原始實現也執行此操作 — 代碼隱藏類只是一個與分析的 .aspx 文件類定義合并的部分類。它簡單有效,但遺憾的是它不夠靈活。該模型的問題在于,預編譯的二進制程序集中的代碼隱藏文件不再能夠與完整的 .aspx 文件一起部署,因為它們現在必須同時編譯(使用部分類的一個限制是,一個類的所有部分必須在單個編譯中合并,而且類定義無法跨越程序集)。對于許多開發人員而言,該限制是無法接受的,因為他們已經習慣于將二進制代碼隱藏程序集與完整的 .aspx 文件一起部署,后者隨后會進行適當的更新而不必重新編譯。實際上,這就是默認情況下 Visual Studio .NET 2003 中使用的模型,而且在實踐中非常流行。

由于重新引入了繼承模型并將部分類移到基類中,.aspx 文件現在可以從代碼隱藏類中進行獨立部署和編譯。為此,您需要某種方式在編譯或部署過程中生成同輩的部分類,后者包含控件變量聲明,因為在過去這一直是針對請求進行的。走近 ASP.NET 編譯器。

在 ASP.NET 2.0 中,ASP.NET 編譯器 (aspnet_compiler.exe) 最初作為完全預編譯整個站點的一種方式引入,從而使得只部署二進制程序集成為可能(甚至也對 .aspx 和 .ascx 文件進行預編譯)。這是非常吸引人的,因為它消除了發出請求時的任何按需編譯,從而消除了目前在一些站點上可以看到的第一個部署后點擊。它也使得對已部署站點進行修改更加困難(因為您無法打開 .aspx 文件并更改內容),當部署只想通過標準部署過程更改的應用程序時,這是很吸引人的。ASP.NET 2.0 的發布版本提供的編譯器支持僅支持二進制的部署模型,但是它也進行了增強以支持可更新的部署模型,其中站點中的所有源代碼預編譯為二進制程序集,但是所有 .aspx 和 .ascx 文件都基本保持完整,以便可以在服務器上進行更改(針對 .aspx 和 .ascx 文件的更改,涉及移除的 CodeFile 屬性以及進行修改以包括程序集名的 Inherits 屬性)。由于在代碼隱藏模型中重新引入了繼承,因此該模型是可能的。這樣,包含控件聲明的同輩部分類可以獨立于實際的 .aspx 文件類定義生成和編譯。


圖 4 使用 aspnet_compiler.exe 進行二進制部署

圖 4 顯示使用二進制部署選項對 aspnet_compiler.exe 實用工具的調用,以及針對部署目錄的結果輸出。請注意,該部署目錄中的 .aspx 文件只是沒有內容的標記文件。它們之所以位于那里,是為了確保 IIS 應用程序中 .aspx 擴展的“Check that file exists”選項進行設置后,帶有終結點名稱的文件可用。PrecompiledApp.config 文件用于跟蹤應用程序的部署方式,以及 ASP.NET 是否需要在請求時編譯任何文件。要生成“可更新的”站點,需要將一個 -u 添加到命令行,得到的 .aspx 文件將包含它們的原始內容(而不是空的標記文件)。請注意,該功能也可以通過 Visual Studio 2005 的 Build | Publish Web Site 菜單項以圖形方式訪問,如圖 5 所示。該命令行工具和 Visual Studio 都依賴于 System.Web.Compilation 命名空間的 ClientBuildManager 類提供該功能。


圖 5 Visual Studio 2005 中的 Build | Publish Web Site 工具

使用手邊的 aspnet_compiler 實用工具,您無需擔心應用程序的大體部署方式就可使其運行,因為任何站點都能以下面三種方式之一進行部署 — 全源、全二進制或可更新(二進制文件中的源代碼和源代碼中的 .aspx 文件)— 無需對開發中使用的頁面屬性或代碼文件進行任何更改。這在以前的 ASP.NET 版本中是不可能的,因為您必須在開發時決定是否使用 src 屬性來引用代碼隱藏文件,或者預編譯它們并將程序集部署到 /bin 目錄。完整的二進制部署甚至不是一個選項。

程序集生成

既然編譯為程序集可以在三種情況下發生(由開發人員顯式進行,使用 aspnet_compiler.exe,或者在請求處理中進行),因此了解文件到程序集的映射變得更為重要。實際上,根據編寫頁面的方式,您實際上可以得到一個應用程序,在作為全源或全二進制部署時,該應用程序可以正常工作,但在使用可更新的切換進行部署時,卻編譯失敗。

模型 ASP.NET 通常使用 App_Code 目錄內容的單獨程序集以及 global.asax 文件(如果存在),然后將每個目錄中的所有 .aspx 頁編譯為單獨的程序集。(如果同一目錄中的頁面是以不同語言制作的,或者它們通過 @ Reference 指令彼此依賴,則它們也可以形成單獨的程序集。)用戶控件和母版頁通常也獨立于 .aspx 頁進行編譯。例如,如果要在一個項目中包含 Visual Basic? 和 C# 源代碼,也可以配置 App_Code 目錄來創建多個程序集。在程序集創建的細節中有一些細微差別,這取決于您所選的部署模式。圖 6 描述特定 Web 站點的組件,該 Web 站點基于您要使用的部署模式編譯為單獨的程序集。(請注意,我要忽略資源、主題和瀏覽器目錄,因為它們不包含代碼,雖然它們也編譯為單獨的程序集。正如前面提到的,目標程序集也因語言的不同和引用依賴項而異。)

 
Deployment Mode
  All SourceAll BinaryUpdatable (mixed)
What compiles into a unique assembly App_Code directoryApp_Code directory
global.asax
.ascx and associated codebehind file (separate assembly for each user control)
.master and associated codebehind file (separate assembly for each master page)
All .aspx files and their code-behind files in a given directory (separate assembly per directory)
App_Code directory
global.asax
.ascx and .master files and their associated codebehind files
All .aspx files and their code-behind files in a given directory (separate assembly per directory)
App_Code directory (D)
global.asax (R)
.ascx and .master files (R)
codebehind files for .ascx and .master files (D)
All .aspx files in a given directory (separate assembly per directory) (R)
All codebehind files associated with .aspx files in a given directory (separate assembly per directory) (D)
When it's compiledRequest timeDeployment time(R) = Compiled at request time
(D) = Compiled at deployment time

程序集生成的另一個技巧是,使用 aspnet_compiler 的 -fixednames 選項請求將每個 .aspx 文件編譯為單獨的程序集,該程序集的名稱跨編譯器的不同調用保持一致。如果您想更新單個頁面而不修改部署站點上的其他程序集,這是很有用的。它也可以為任何大型站點生成大量程序集,因此您一定要在使用該選項之前測試您的部署。

如果您覺得這比較復雜,我可以告訴您它的優點,即您無需花費大量時間考慮將哪些文件映射為單獨的程序集。.aspx 文件一直在最后進行編譯,并一直包括對生成的所有其他程序集的引用,因此,無論您選擇哪種部署模型,它通常都會正常工作。

在部署中,可能實際影響您在頁面中制作代碼的方式的一個重要區別是,當使用可更新部署時編譯中的分離。當部署可更新站點時,代碼隱藏文件在部署之前編譯為單獨的程序集。從 .aspx 文件生成的類不進行編譯,除非作出對目錄中文件的實際請求。這與二進制部署(其中所有文件在部署之前編譯)以及源部署(其中所有文件在請求時編譯)形成了鮮明對比。以下這一簡單的示例解釋這是如何引出問題的,請考慮圖 7 中帶有嵌入屬性的用戶控件(.ascx 文件),以及一個使用該控件并從其代碼隱藏類設置該屬性的相關頁面。

MyUserControl.ascx
<%@ Control Language="C#"%>
<script runat="server">
public string Color
{
get { return (string)ViewState["color"] ?? "white"; }
set { ViewState["color"] = value; }
}

protected override void OnLoad(EventArgs e)
{
_mainPanel.BackColor = System.Drawing.Color.FromName(Color);
base.OnLoad(e);
}
</script>

<asp:Panel runat="server" ID="_mainPanel">
<h2>Some text</h2>
Other text
</asp:Panel>

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Src="MyUserControl.ascx" TagName="MyUserControl" TagPrefix="uc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<uc1:MyUserControl id="MyUserControl1" runat="server">
</uc1:MyUserControl></div>
</form>
</body>
</html>

Default.aspx.cs
using System;
using System.Web.UI;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
MyUserControl1.Color = "purple";
}
}

圖 7 中的頁面將以源或二進制部署模式編譯并運行,但是當作為可更新站點部署時將無法編譯,原因是該用戶控件 Color 屬性的定義在部署時不可用(該限制也存在于 1.x 模型中)。要避免此類問題發生,通常您可以將所有代碼放在代碼隱藏文件中,或者干脆不使用代碼隱藏文件,將代碼直接放在 .aspx 和 .ascx 文件中。

有關文件到程序集映射的另一個注意事項是,使用內部關鍵字防止外部程序集訪問類中的方法,這可能只在某些部署方案中奏效而在其他方案中卻不然,這是因為存在不同的程序集映射選項。除非您提前計劃要使用哪個部署選項,否則最好避免在頁面中使用內部方法并繼續使用類型范圍的保護關鍵字:公共、受保護和私有。

小結

對于 ASP.NET 開發人員而言,ASP.NET 2.0 中的新代碼隱藏模型既熟悉又陌生。之所以說熟悉是因為,它仍然使用繼承將代碼隱藏類與其 .aspx 生成的類定義相關聯,而之所以說陌生是因為,諸如部分類這樣的元素和控件成員變量聲明的隱式生成都是基本的轉換。實際上,您可能不會注意到用法上的許多差別,但是無論您何時進行非一般的操作(例如,創建一個通用基 Page 類,或者將代碼隱藏與內聯代碼模型混合),了解本文描述的類關系和程序集映射都是很重要的。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗