基於功能更豐富的基礎類構建您自己的 ASP.NET 頁面

來源:互聯網
上載者:User

摘要:通過繼承可以在通用 Microsoft ASP.NET 類(例如 Page 類)中添加功能。這為您提供了一個公用場所,使您可以添加功能並將功能部署到所有頁面上。在本文中,Dino 將向您介紹如何添加頁面重新整理處理、對冗長進程的支援以及使用 Page 類設定焦點控制項。(本文包含一些指向英文網站的連結。請注意,在樣本檔案中,程式員的注釋使用的是英文,本文中將其譯為中文是為了便於讀者理解。)

下載本文的原始碼。

所有 Microsoft ASP.NET 頁面均來自由 System.Web.UI.Page 類表示的通用基礎頁面。為了處理對 .aspx 資源的請求,ASP.NET 運行庫將建立一個動態類,並使該類繼承基礎 Page 類,或繼承其他反過來又繼承基礎 Page 類的類。如果在支援內含代碼模型的 Microsoft Visual Studio .NET 2003 項目中建立頁面,動態建立的 Page 類將繼承內含代碼類,而內含代碼類反過來又繼承基礎 Page 類。

基礎 Page 類實現典型的 ASP.NET 頁面生命週期(載入-回傳-渲染周期),並為衍生頁面提供一組預定義的成員和功能,例如,回傳檢測、指令碼插入、渲染和檢視狀態管理。

總而言之,System.Web.UI.Page 類只是一個基礎類,可用來定義一組通用的、最基本的功能和行為。在特定的應用程式中,頁面很可能針對更多功能,並提供更強大的編程介面。有兩種可能的擴充方式:對頁面基礎結構進行一般意義上的增強,或者針對應用程式而改進功能。前一種擴充的樣本是用來表示頁面菜單和功能表項目的屬性。針對應用程式的頁面通常是根據由靜態地區、通用地區和可自訂地區組成的邏輯“母片”頁設計的。這些地區的內容可能因頁面而異,它們通常通過模板、預留位置和使用者控制項進行填充。

請注意,在 Microsoft ASP.NET 2.0 中,主版頁面的引入大大簡化了使用自訂和可程式化屬性和成員針對應用程式而建立頁面的過程。

但是,如果您需要為頁面建立功能更豐富、更複雜的基礎結構,應該怎麼辦?如何指示所有頁面提供其他系統層級的功能,例如,檢測 F5(重新整理)鍵的功能?實現給定技巧所需的代碼始終可以與每個特定頁面的作業碼(包含在內含代碼類中的代碼)合并在一起。但是,即使只實現了兩三個功能,產生的程式碼的品質已經開始類似於大家唾棄的意大利麵條式代碼了,這是非常危險的。您必須尋找其他方法。

構建功能更豐富的基礎類

一種更好的方法是建立新的基礎 Page 類,用它來代替標準 System.Web.UI.Page 類。在本文中,我將介紹幾項通用功能以及它們的一般實現,並將這些功能裝入一個新的、功能更豐富的 Page 類中。我要介紹的功能包括:

  • 檢測 F5(重新整理)鍵。

  • 啟動並控制一個需要立即向使用者發送反饋頁面的冗長操作。

  • 在載入頁面時設定輸入焦點。

如果您經常光顧專門介紹 ASP.NET 的新聞群組和社區網站,而且閱讀了大量文章、書籍和新聞稿,那麼您可能已經知道如何在 ASP.NET 1.x 應用程式上下文中分別實現上述各項功能。這裡的問題是,如何通過一個組件和一個進入點同時提供所有這些功能。

通過定義自訂 Page 類,您只需付出最少的努力便可在新的 .aspx 頁面上提供所有其他功能和服務並獲得最大回報。使用 Visual Studio .NET 2003 建立的內含字碼頁面的聲明方式如下:

public class WebForm1 :System.Web.UI.Page{:}

要使 Web Form類繼承非預設的 Page 類,只需按照如下所示更改基礎類型。

public class WebForm1 :Msdn.Page{:}

如果頁面不是在 Visual Studio .NET 2003 中建立的或者使用了內嵌代碼,那麼可以通過 @Page 指令中的 Inherits 屬性為該版面設定基礎類型。

<% @Page Inherits="Msdn.Page" ... %>

您可以按照此處所述逐個頁面地更改 ASP.NET 頁面的基礎類型,也可以使用設定檔中的 <pages> 節點。

<pages pageBaseType="Msdn.Page" />

<pages> 節點的 pageBaseType 屬性指示要用作所有動態建立的 Page 類的基礎類型的類的名稱和程式集。預設情況下,該屬性在 machine.config 檔案中被設定為 System.Web.UI.Page。您可以在應用程式的 web.config 檔案中覆蓋該設定。

下面讓我們看看如何在實際應用中實現上述各項功能,以及如何將這些功能封裝到一個無所不包的類中。

檢測瀏覽器重新整理

概括介紹了當使用者按下瀏覽器中的 F5 鍵重新整理當前頁面時,對這一過程進行檢測所需的操作步驟。頁面重新整理是瀏覽器對特定使用者操作(按 F5 鍵或單擊“重新整理”工具列按鈕)的響應。頁面重新整理操作是瀏覽器內部的一種操作,因為瀏覽器不會為事件或回調發出任何外部通知。從技術上講,頁面重新整理是通過“簡單”重複最新請求來實現的。換句話說,瀏覽器將緩衝已處理的最新請求,並在使用者單擊頁面重新整理鍵時重新發布已處理的請求。

正是因為所有瀏覽器(據我所知)不會為頁面重新整理事件提供任何類型的通知,所以伺服器端的代碼(例如,ASP.NET、典型 ASP 或 ISAPI DLL)根本無法區分重新整理請求與一般的提交回傳請求。為了協助 ASP.NET 檢測和處理頁面重新整理,您需要構建能夠使兩個完全相同的請求看起來不同的環境機制。

瀏覽器通過重新發送上次發送的 HTTP 承載來實現重新整理,並使副本看起來與原始版本不同(這是一項額外服務,需要添加額外的參數並且 ASP.NET 頁面必須能夠緩衝這些參數)。提供了我要構建的子系統的詳細視圖。

1 為使重新整理請求看起來與回傳/提交請求不同而設定的機制

會話上下文中處理的每個請求獲得一個唯一且遞增的票證號碼。ASP.NET 頁面在產生響應之前產生票證,並將其儲存在一個自訂的隱藏欄位中發送給瀏覽器。當使用者提交新請求(從而導致回傳顯示的頁面)時,隱藏欄位(如果有)將自動附著到伺服器請求中。

在 Web 服務器上,新的 HTTP 模組將截取 AcquireSessionState 事件,從隱藏欄位中檢索當前票證,並將其與內部緩衝的上次處理的票證 ID 進行比較。(上次處理的票證儲存在工作階段狀態中。)如果當前票證大於上次處理的 ID,或者如果這兩個值都為零,則說明請求是一般的提交或回傳。除此之外,重新整理 HTTP 模組不會執行其他動作,並原封不動地傳遞請求。

如果上次處理的票證大於或等於當前票證,則將請求標識為頁面重新整理。在這種情況下,HTTP 模組將只在該請求的 HTTP 內容相關的 Items 集合中建立一個新條目。在 ASP.NET 中,HttpContext 對象表示請求的上下文,並在請求的整個生命週期中始終存在。HttpContext 對象的 Items 屬性是一個集合,可由 HTTP 模組、工廠處理常式和處理常式使用,用於將自訂資訊轉寄給實際的頁面對象。Items 集合中儲存的所有內容對處理當前請求的過程中涉及到的所有組件均可見。這些資訊的生命週期與請求的生命週期相同,因此,一旦產生響應,所有資料都將被銷毀。通過使用 HttpContext.Current 靜態屬性,可以從該過程中涉及到的任何類訪問當前請求的 HTTP 上下文。

重新整理 HTTP 模組將在 Items 集合中建立名為 IsPageRefreshed 的新條目。該條目的布爾值表明是通過一般的提交/回傳請求頁面還是通過重新整理請求頁面。下面的列表顯示了重新整理 HTTP 模組的實現。

using System;using System.Web;using System.Web.SessionState;namespace Msdn{public class RefreshModule :IHttpModule {// IHttpModule::Initpublic void Init(HttpApplication app){// 註冊管道事件app.AcquireRequestState +=new EventHandler(OnAcquireRequestState);}// IHttpModule::Disposepublic void Dispose() {}// 確定是否正在處理 F5 或後退/前進操作private void OnAcquireRequestState(object sender, EventArgs e) {// 訪問 HTTP 上下文HttpApplication app = (HttpApplication) sender;HttpContext ctx = app.Context;// 檢查 F5 操作RefreshAction.Check(ctx);return;}}}

RefreshAction 類包含用來確定當前請求是否是頁面重新整理的邏輯。如果確定為頁面重新整理,HttpContextItems 集合中將包含一個新條目:IsPageRefreshed 設定為 true

public static void Check(HttpContext ctx){// 初始化票證欄位EnsureRefreshTicket(ctx);// 讀取會話中上次處理的票證(從會話中)int lastTicket = GetLastRefreshTicket(ctx);// 讀取當前請求的票證(從隱藏欄位中)int thisTicket = GetCurrentRefreshTicket(ctx);// 比較兩個票證if (thisTicket > lastTicket ||(thisTicket==lastTicket && thisTicket==0)){UpdateLastRefreshTicket(ctx, thisTicket);ctx.Items[PageRefreshEntry] = false;}elsectx.Items[PageRefreshEntry] = true;}

隱藏欄位和會話欄位的名稱在 RefreshAction 類中被設定為公用常量,並且可以在該類的外部使用。

應用程式頁面如何利用此機制?什麼時候檢測頁面重新整理真正有用?HTTP 模組並不阻止任何請求,它只為最終 ASP.NET 頁面添加更多資訊以便處理請求。添加的資訊包括表示頁面重新整理的布爾值。

使用頁面重新整理事件

Web 頁的使用者通常只執行幾個操作,而且從某種程度上講,執行這些操作時心情都很愉快。這些操作包括“後退”、“前進”、“停止”和“重新整理”。但這些操作構成了一種 Internet 瀏覽器的標準工具包。截取以及細分這些操作可能會對普遍認可的 Internet 操作帶來某種“局限性”。對使用者可能產生負面影響。

另一方面,當使用者重新整理當前頁面或退回到先前訪問的頁面時,會向伺服器提交已處理過的請求,這有可能會打斷應用程式狀態的一致性。在這種情況下,也可能對應用程式產生負面影響。

請設想以下情況:

您通過 DataGrid 顯示資料,並在每一行中提供一個按鈕,供使用者刪除所表示的資料行。儘管這是很常見的做法(輕輕點擊,即可刪除當前應用程式中實現的資料),但這種做法極其危險。使用者很容易由於失誤而單擊了錯誤的按鈕,從而破壞資料的一致性,而且如果他們在刪除(不管是有意還是無意)之後重新整理頁面,則很可能會刪除第二個行。

當您重新整理頁面時,瀏覽器只重複上次發布的內容。從 ASP.NET 運行庫的角度來看,只有一個新請求要處理。ASP.NET 運行庫無法區分一般的請求和意外重複的請求。如果採取離線工作的方式,並按記憶體中儲存的 DataSet 中的位置刪除記錄,則很可能會多刪除一條記錄。如果上一個操作以 INSERT 結束,重新整理頁面更有可能會添加一條記錄。

這些樣本清楚地暴露出某些有爭議的設計問題,但它們反映了完全可能的情況。那麼,阻止頁面重新整理最好的方式是什麼呢?

本文前面討論的機制可以預先處理請求,並確定是否正在重新整理頁面。這些資訊通過 HttpContext 對象傳遞給頁面處理常式。在頁面中,開發人員可以使用以下代碼檢索這些資料。

bool isRefresh = (bool) HttpContext.Current.Items["IsPageRefreshed"];

但更好的做法是,如果使用自訂的、更有針對性的 Page 類,則可以將資料封裝到一個更便於使用的屬性中,即封裝到 IsPageRefresh 屬性中。

public bool IsPageRefresh {get {object o =HttpContext.Current.Items[RefreshAction.PageRefreshEntry];if (o == null)return false;return (bool) o;}}

通過使 Page 類繼承新的、功能更豐富的基礎類(本例中為 Msdn.Page),可以通過新屬性瞭解發出請求的真正原因。以下樣本顯示了如何?不應在頁面重新整理時重複的某個關鍵操作。

void AddContactButton_Click(object sender, EventArgs e) {if (!IsPageRefresh)AddContact(FName.Text, LName.Text);BindData();TrackRefreshState();}

僅當在不重新整理頁面時才添加新連絡人,換句話說,僅當使用者按照常規方式單擊“Add-Contact”(新增連絡人...)按鈕時才會新增連絡人...。上述代碼片斷中有一個很奇怪的 TrackRefreshState 方法,它的作用是什麼呢?

該方法更新票證計數器,並確保新頁面響應包含帶有最新票證的隱藏欄位。在本例中,通過將工作階段狀態中儲存的值遞增一來擷取下一個票證。(這裡只是隨便使用了工作階段狀態,最好不要使用工作階段狀態,而使用更具擴充性的提供者模型,就像在 ASP.NET 2.0 中一樣。)

但是,關於 TrackRefreshState 方法(這是有意命名的,以便於大家回想起更熟悉的 TrackViewState 方法),主要有一點要說明。通過調用該方法,除了可以添加其他資訊外,還可以將帶有當前請求票證的隱藏欄位添加到頁面響應中。如果沒有隱藏欄位(參見圖 1),重新整理機制將無法檢測下一個回傳是重新整理還是提交。換句話說,通過在回傳事件處理常式中調用 TrackRefreshState,使得系統知道您要跟蹤該操作(而且只跟蹤該操作),以確定是否為頁面重新整理。這樣,您只跟蹤可能會出錯的頁面重新整理,而且並不是所有頁面重新整理都會在會話生命週期內發生。

要利用頁面重新整理功能,只需在 Microsoft Visual Studio .NET 項目中添加一個新頁面,然後開啟內含代碼檔案並將頁面的基礎類更改為 Msdn.Page。接下來,在您執行不應重新整理的操作時調用 TrackRefreshState(Msdn.Page 類的新的公用方法)。使用新的布爾屬性 IsPageRefresh 檢查重新整理狀態。

使使用者在冗長操作過程中獲得愉快體驗

有關如何在 Web 上跟蹤特別耗時的操作這個問題,已經通過多篇文章和多次演講為大家提供了各種解決方案。我所說的“耗時的操作”是指 Windows 表單方案中通常需要進度列的所有操作。在 Web 頁上顯示進度列很容易出現問題。進度列應該能夠與伺服器通訊,以擷取有助於更新進度的資訊。此外,此操作不應通過回傳或重新整理元標記來完成,以免完全重新整理頁面。在任何情況下,均需要具備強大的動態超文字標記語言 支援。

要使使用者在冗長操作過程中獲得愉快體驗,相對簡單的方法就是顯示一個中間反饋頁面,為使用者顯示一些等待訊息,最好是帶點動畫。此頁面完全與上下文無關,但無疑要比載入新頁面之前在空白頁面上長時間顯示一個沙漏更有用。

要在冗長操作過程中顯示一些反饋,有一種簡單而有效方法,它可以概括成以下幾個步驟:

  • 一旦使用者通過單擊開始該任務,便將使用者重新導向到反饋頁面。反饋頁面必須知道實際執行任務的頁面的 URL。此 URL(包括工作階段狀態)可以通過查詢字串進行傳遞,也可以放置在可訪問的資料存放區中。

  • 開始載入反饋頁面後,再重新導向到工作頁面。這種情況下,重新導向是由頁面的 onload Javascript 事件中的指令碼完成的。瀏覽器載入並顯示反饋頁面,然後指向工作頁面。頁面開始執行冗長的任務,同時為使用者顯示反饋頁面。

  • 根據需要,反饋頁面可以很複雜並包括許多 UI 元素。它可以包含“請稍候...”訊息或顯示動畫 GIF,或者藉助某些動態超文字標記語言 功能,顯示某些看起來像是一個真正進度列的內容。

我特意建立了一個 LengthyAction 類來協助管理冗長任務的開始。

private const string UrlFormatString = "{0}?target={1}";public static void Start(string feedbackPageUrl,string targetPageUrl){// 準備反饋頁面的 URLstring url = String.Format(UrlFormatString,feedbackPageUrl, targetPageUrl);// 將調用重新導向到反饋頁面HttpContext.Current.Response.Redirect(url);}

該類的特點是只有一個靜態方法,即 StartStart 方法擷取反饋頁面和目標頁面(即執行任務的頁面)的 URL。該方法將兩個參數合并成一個 URL 並進行重新導向。

反饋頁面可以包含您希望的任何使用者介面,但必須滿足幾個關鍵的要求。該頁面必須能夠檢索工作頁面的名稱,並提供一個可能的自動機制,以便通過指令碼重新導向到工作頁面。我定義了一個自訂的基礎 Page 類,並將這些功能內建在該類中。這樣做時,我必須進行一些假定。特別是,我的實現假定工作頁面的名稱使用大家熟知的屬性名稱 target 通過查詢字串進行傳遞。目標頁面的名稱儲存在名為 TargetURL 的公用屬性中。此外,反饋頁面提供名為 GetAutoRedirectScript 的函數。此函數的目的是返回通過指令碼實現重新導向所需的指令碼代碼。

public string GetAutoRedirectScript() {return String.Format("location.href='{0}';", TargetUrl);}

為了使問題儘可能地簡單,FeedbackBasePage 類還尋找名為 Body 的通用 HTML 控制項。這與您從以下標記中擷取的完全一樣。

<body runat="server" id="Body">

如果可以通過簡單的方法為頁面的本文標記編程,FeedbackBasePage 類將找到這種方法並自動添加 onload 屬性;否則,您必須手動添加 onload 屬性。要使反饋頁面正常工作,此屬性是必需的。

HtmlGenericControl body = FindControl(BodyId) as HtmlGenericControl;if (body != null)body.Attributes["onload"] = GetAutoRedirectScript();

最後提供給瀏覽器的標記代碼如下所示。

<body onload="location.href='lengthyop.aspx'">

讓我們看看使用本文中討論的類實現冗長操作需要執行哪些步驟。

首先引用所需的程式集,然後為觸發操作的單擊按鈕編寫以下事件處理常式。

void ButtonLengthyOp_Click(object sender, EventArgs e) {LengthyAction.Start("feedback.aspx", "work.aspx");}

接下來,在項目中添加反饋頁面。這是一個常規 Web Form頁,您可以按照上文所述修改其 <body> 標記,並將基礎類更改為 FeedbackBasePage。在您單擊按鈕開始進程之後以及顯示結果之前,將顯示反饋頁面的使用者介面,如所示。

2 冗長操作的順序

在本例中,我使用了一種跨頁回傳,這對特別冗長的操作來說是一種更普遍的方案。但是,這引發了傳遞檢視狀態以及工作頁面完成其任務通常所需的參數的問題。您可以使用工作頁面的查詢字串將序列化的對象版本串連起來,或者將所有內容都儲存在 ASP.NET 緩衝或 Session 對象中。這種情況下不能使用 HTTP 上下文,因為該操作涉及多個 HTTP 要求,每個請求都有一個不同的項目集。

請注意,反饋頁面的 URL 包含調用的某些細節,並且可能如下所示。

feedback.aspx?target=work.aspx?param1=123&param2=hello

要隱藏這些細節,您可以定義一個自訂 HTTP 處理常式,並將其綁定到您認為更合適的虛擬 URL。該 HTTP 處理常式可以從緩衝或工作階段狀態中檢索所需的資訊(包括反饋頁面和工作頁面的名稱)。

設定焦點控制項

ASP.NET 2.0 提供了一個非常好的新功能,允許您指定首次顯示頁面時將哪個輸入控制項設定為焦點。這是一種靈活的功能,可以減少使用者通過單擊開始操作的負擔,例如,在文字框中單擊開始輸入資料。

要將 HTML 元件指定為輸入焦點,您需要一小段 Javascript 代碼。首先聲明一點:這不是尖端的火箭科學,您可以輕鬆地將這段 Javascript 代碼作為內嵌代碼添加到 <body> 標記的 onload 屬性中。但是,在 Page 類上使用 SetFocus 方法確定伺服器上的焦點控制項的名稱確實是前進了一大步。實際上,您可以在 ASP.NET 2.0 中使用以下代碼。

void Page_Load(object sender, System.EventArgs e) {SetFocus("TheFirstName");}

當顯示頁面時,名為 TheFirstName 的輸入控制項將成為焦點。此方法便捷有效,但如何在 ASP.NET 1.x 中對其進行編碼?

同樣,實現此功能的技巧已為業界人士所熟知,也可以從 Google 中毫不費力地搜尋到。但問題是,如何將其整合到基礎 Page 類中以便重複使用。

讓我們使用以下聲明來擴充 Msdn.Page 基礎類。

private string m_focusedControl;public void SetFocus(string ctlId) {m_focusedControl = ctlId;}

SetFocus 方法收集控制項的 ID 並將其儲存在內部成員中。在頁面的 PreRender 事件中,調用另一個協助程式函數以構建和插入 Javascript 代碼。

private void AddSetFocusScript(){if (m_focusedControl == "")return;// 添加指令碼以聲明函數StringBuilder sb = new StringBuilder("");sb.Append("<script language=javascript>");sb.Append("function ");sb.Append(SetFocusFunctionName);sb.Append("(ctl) {");sb.Append("  if (document.forms[0][ctl] != null)");sb.Append("  {document.forms[0][ctl].focus();}");sb.Append("}");// 添加指令碼以調用函數sb.Append(SetFocusFunctionName);sb.Append("('");sb.Append(m_focusedControl);sb.Append("');<");sb.Append("/");   // 按照這種方式斷開,以避免誤解...sb.Append("script>");// 註冊指令碼(名稱區分大小寫)if (!IsStartupScriptRegistered(SetFocusScriptName))RegisterStartupScript(SetFocusScriptName, sb.ToString());}

Javascript 代碼可以像動態字串一樣構建,並累積儲存在 StringBuilder 對象中。下一步是將該字串添加到頁面輸出中。在 ASP.NET 中,要在頁面中添加一些用戶端指令碼代碼,必須先在特定頁面層級的集合中註冊該代碼。為此,Page 類提供了幾個 RegisterXxx 方法。每個 RegisterXxx 方法將 Javascript 代碼塊添加到不同的集合中,以便插入到最終頁面標記中的不同位置。例如,RegisterStartupScript 在表單的關閉標記之前插入代碼。而 RegisterClientScriptBlock 在表單的開啟標記之後插入指令碼代碼。重要的是,必須在指令碼中包括 <script> 元素的兩個標記。每個指令碼塊都由一個關鍵字標識,這樣多個伺服器控制項可以使用同一個指令碼塊,而不會將它發送給輸出資料流兩次或多次。

在頁面中,以下 Javascript 代碼塊被插入到表單的關閉標記之前。這樣,它將在初始化後啟動時立即開始運行。

<form>:<script language=javascript>function __setFocus(ctl) {if (document.forms[0][ctl] != null) {document.forms[0][ctl].focus();}}__setFocus('TheFirstName');</script></form>

通過在 Msdn.Page 類上使用 SetFocus 公用方法,您可以在頁面代碼的任何位置決定在瀏覽器中顯示頁面時將哪個控制項作為輸入焦點。更重要的是,您可以根據運行時條件和/或回傳事件作出此決定。

結論

在物件導向的技術(例如 ASP.NET)中,主要優點之一是您可以廣泛使用繼承。通過繼承和改進現有控制項的公用介面,您可以很輕鬆地建立新的自訂伺服器控制項。在衍生類中,您可以替代虛擬方法,從而改變組件的內部行為。將這些物件導向的編程 (OOP) 原則應用於控制項似乎非常自然,非常普遍,但對於表示 ASP.NET 頁的類,情況則不盡然。

不過,頁面繼承廣泛用於構建所請求的每個 .ASPX 頁面的可執行表示。內含字碼頁面是指從基礎 System.Web.UI.Page 類繼承的頁面。為什麼不定義一個中間 Page 類,為應用程式特定的頁面提供功能更豐富的基礎呢?

這正是本文所闡述的問題。我在本文中介紹了許多開發人員或多或少都能成功實現的三個常見功能,即檢測重新整理鍵、控制冗長操作以及將控制項指定為輸入焦點,還介紹了如何將這三個功能全部封裝到一個無所不包的 Page 類的上下文中。

內含代碼和內嵌代碼應用程式中使用了新的 Page 類(Msdn.Page 類)來取代基礎 Page 類,這個新類以方便且可重複使用的方式為開發人員提供了更多準系統。功能更豐富的基礎 Page 類是為 ASP.NET 應用程式構建更可靠平台的一個裡程碑。實際應用程式的所有頁面都應從自訂類開始構建,以此來驗證 Page 類的功能。

參考資料

  • Programming Microsoft ASP.NET

  • Building Web Solutions with ASP.NET and ADO.NET

  • Introducing ASP.NET 2.0

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.