逐步解說:使用用戶端應用程式服務
這個主題描述如何建立使用用戶端應用程式服務的 Windows 應用程式,以驗證使用者並擷取使用者角色與設定。
在這個逐步解說中,您會執行下列工作:
建立 Windows Form 應用程式,並且使用 Visual Studio 專案設計工具啟用與設定用戶端應用程式服務。
建立簡單的 ASP.NET Web Service 應用程式,以裝載應用程式服務並且測試用戶端組態。
將表單驗證加入至應用程式。 一開始會使用硬式編碼的使用者名稱與密碼測試服務。 然後將登入表單指定為應用程式組態中的認證提供者予以加入。
加入以角色為基礎的功能,並且只針對具有「管理員」角色的使用者啟用與顯示按鈕。
存取 Web 設定。 一開始會在專案設計工具的 [設定] 頁面中載入已驗證 (測試) 使用者的 Web 設定。 然後使用 Windows Form 設計工具將文字方塊繫結至 Web 設定。 最後再將已修改的值儲存回伺服器。
實作登出。 您會將登出選項加入至表單,然後呼叫登出方法。
啟用離線模式。 您會提供核取方塊供使用者指定其連接狀態。 然後使用此值指定用戶端應用程式服務提供者,是否使用本機快取資料而不是存取其 Web 服務。 最後當應用程式返回線上模式時,再重新驗證目前的使用者。
必要條件
您需要下列元件才能完成此逐步解說:
- Visual Studio 2008。
建立用戶端應用程式。
首先您要做的是建立 Windows Form 專案。 這個逐步解說會使用 Windows Form,因為很多人已熟悉這項功能,而且程序類似 Windows Presentation Foundation (WPF) 專案。
建立用戶端應用程式並啟用用戶端應用程式服務
在 Visual Studio 中選取 [檔案 | 新增 | 專案] 功能表選項。
在 [新增專案] 對話方塊中,請展開 [專案類型] 窗格中的 [Visual Basic] 或 [Visual C#] 節點,然後選取 [Windows] 專案類型。
請確定已選取 [.NET Framework 3.5],然後選取 [Windows Form 應用程式] 範本。
將專案的 [名稱] 變更為 ClientAppServicesDemo,然後按一下 [確定]。
在 Visual Studio 中隨即開啟新的 Windows Form 專案。
在 [專案] 功能表上,選取 [ClientAppServicesDemo 屬性]。
[專案設計工具] 隨即出現。
在 [服務] 索引標籤上,選取 [啟用用戶端應用程式服務]。
請確定已選取 [使用表單驗證],然後將 [驗證服務位置]、[角色服務位置] 以及 [Web 設定服務位置] 設定為 https://localhost:55555/AppServices。
在 Visual Basic 的 [應用程式] 索引標籤中,將 [驗證模式] 設定為 [由應用程式定義]。
設計工具會將指定的設定儲存在應用程式的 app.config 檔中。
此時,應用程式會設定為從相同的主機存取這三種服務。 在下一章節,將建立當做簡單 Web 服務應用程式的主機,以便讓您測試用戶端組態。
建立應用程式服務主機
在此章節中,您將建立簡單的 Web 服務應用程式,以便從本機 SQL Server Compact 3.5 資料庫檔案存取使用者資料。 接著,您將會使用 ASP.NET 網站管理工具填入資料庫。 此簡單的組態可讓您快速地測試用戶端應用程式。 除此之外,您還可以設定 Web 服務主機,以便從完整的 SQL Server 資料庫存取使用者資料,或透過自訂 MembershipProvider 和 RoleProvider 類別來存取使用者資料。 如需詳細資訊,請參閱 建立及設定 SQL Server 的應用程式服務資料庫。
在下列程序中,將建立與設定 AppServices Web 服務。
建立與設定應用程式服務主機
在 [方案總管] 中,選取 ClientAppServicesDemo 方案,然後在 [檔案] 功能表上選取 [加入 | 新增專案]。
在 [加入新的專案] 對話方塊中,請展開 [專案類型] 窗格中的 [Visual Basic] 或 [Visual C#] 節點,然後選取 [Web] 專案類型。
請確定已選取 [.NET Framework 3.5],然後選取 [ASP.NET Web Service 應用程式] 範本。
將專案的 [名稱] 變更為 AppServices,然後按一下 [確定]。
新的 ASP.NET Web 服務應用程式專案隨即加入至方案,而編輯器中會出現 Service1.asmx.vb 或 Service1.asmx.cs 檔案。
注意事項 本範例中不會使用 Service1.asmx.vb 或 Service1.asmx.cs 檔案。 如果您想要保持工作環境的整齊,可以將其關閉,並將其從 [方案總管] 中刪除。
在 [方案總管] 中選取 AppServices 專案,然後在 [專案] 功能表上選取 [AppServices 屬性]。
[專案設計工具] 隨即出現。
在 [Web] 索引標籤上,請確定已選取 [使用 Visual Studio 程式開發伺服器]。
選取 [指定通訊埠],指定值為 55555,然後將 [虛擬路徑] 設定為 /AppServices。
儲存所有檔案。
在 [方案總管] 中,開啟 Web.config 然後尋找 <system.web> 開頭標記。
在 <system.web> 標記 (Tag) 之前加入下列標記 (Markup)。
在這個標記中的 authenticationService、profileService 以及 roleService 項目會啟用與設定應用程式服務。 基於測試目的,authenticationService 項目的 requireSSL 屬性會設定為 "false"。 profileService 項目的 readAccessProperties 和 writeAccessProperties 屬性 (Attribute) 表示 WebSettingsTestText 屬性 (Property) 是讀取/寫入。
注意事項 在實際執行程式碼中,一定要透過 Secure Sockets Layer (SSL,使用 HTTPS 通訊協定) 存取驗證服務。 如需設定 SSL 的詳細資訊,請參閱設定 Secure Sockets Layer (IIS 6.0 操作手冊) (英文)。
<system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" requireSSL = "false"/> <profileService enabled="true" readAccessProperties="WebSettingsTestText" writeAccessProperties="WebSettingsTestText" /> <roleService enabled="true"/> </webServices> </scripting> </system.web.extensions>
在 <system.web> 開頭標記 (Tag) 之後加入下列標記 (Markup),將其包含在 <system.web> 項目中。
profile 項目會設定名為 WebSettingsTestText 的單一 Web 設定。
<profile enabled="true" > <properties> <add name="WebSettingsTestText" type="string" readOnly="false" defaultValue="DefaultText" serializeAs="String" allowAnonymous="false" /> </properties> </profile>
在下列程序中,您會使用 [ASP.NET 網站管理工具] 來完成服務組態及填入本機資料庫檔案。 您將新增兩個名為 employee 和 manager 的使用者,屬於兩個名稱相同的角色。 使用者密碼分別為 employee! 和 manager!。
若要設定成員資格和角色
在 [方案總管] 中選取 [AppServices] 專案,然後在 [專案] 功能表上選取 [ASP.NET 組態]。
[ASP.NET 網站管理工具] 隨即出現。
按一下 [安全性] 索引標籤上的 [使用安全性設定精靈,逐步設定安全性]。
[安全性設定精靈] 隨即出現,並顯示 [歡迎使用] 步驟。
按一下 [下一步]。
[選取存取方法] 步驟隨即出現。
選取 [從網際網路]。 此步驟會將服務設定為使用表單驗證,而不是 Windows 驗證。
按 [下一步] 兩次。
[定義角色] 步驟隨即出現。
選取 [啟用這個網站的角色]。
按一下 [下一步]。 [建立新角色] 表單隨即出現。
在 [新角色名稱] 文字方塊中輸入 manager,然後按一下 [加入角色]。
具有指定值的 [現有角色] 資料表隨即出現。
在 [新角色名稱] 文字方塊中,以 employee 取代 manager,然後按一下 [加入角色]。
[現有角色] 資料表中隨即出現新的值。
按一下 [下一步]。
[加入新的使用者] 步驟隨即出現。
在 [建立使用者] 表單中,指定下列值。
使用者名稱
manager
密碼
manager!
確認密碼
manager!
電子郵件
manager@contoso.com
安全性問題
manager
安全性解答
manager
按一下 [建立使用者]。
成功訊息隨即出現。
注意事項 [電子郵件]、[安全性問題] 和 [安全性解答] 是表單的必填欄位,但此例中並未使用。
按一下 [繼續]。
[建立使用者] 表單隨即重新出現。
在 [建立使用者] 表單中,指定下列值。
使用者名稱
employee
密碼
employee!
確認密碼
employee!
電子郵件
employee@contoso.com
安全性問題
Employee
安全性解答
employee
按一下 [建立使用者]。
成功訊息隨即出現。
按一下 [完成]。
[網站管理工具] 隨即重新出現。
按一下 [管理使用者]。
使用者清單隨即出現。
針對 [employee] 使用者按一下 [編輯角色],然後選取 [employee] 角色。
針對 [manager] 使用者按一下 [編輯角色],然後選取 [manager] 角色。
關閉裝載 (Host) [網站管理工具] 的瀏覽器視窗。
如果出現訊息方塊,詢問您是否要重新載入修改後的 Web.config 檔案,請按一下 [是]。
這就完成了 Web 服務的設定。 此時,您可以按 F5 執行用戶端應用程式,然後 [ASP.NET 程式開發伺服器] 將會自動與用戶端應用程式一起啟動。 在您結束應用程式後,伺服器會繼續執行,但是會在重新啟動應用程式時重新啟動。 如此可以讓它偵測對 Web.config 所做的任何變更。
若要手動停止伺服器,以滑鼠右鍵按一下工作列上告知區域中的 ASP.NET 程式開發伺服器圖示,然後按一下 [停止]。 有時候在確定是否完全重新啟動時很有用。
加入表單驗證
在下列程序中,會在嘗試驗證使用者的主要表單中加入程式碼,並且在使用者提供無效認證時拒絕存取。 您會使用硬式編碼的使用者名稱與密碼測試服務。
在應用程式程式碼中驗證使用者
在 [方案總管] 中,於 ClientAppServicesDemo 專案中加入 System.Web 組件的參考。
選取 Form1 檔,然後從 Visual Studio 主功能表選取 [檢視 | 程式碼]。
在程式碼編輯器中,將下列陳述式加入至 Form1 檔的頂端。
Imports System.Net Imports System.Threading Imports System.Web.ClientServices Imports System.Web.ClientServices.Providers Imports System.Web.Security
using System.Net; using System.Threading; using System.Web.ClientServices; using System.Web.ClientServices.Providers; using System.Web.Security;
在 [方案總管] 中,按兩下 Form1 顯示設計工具。
在設計工具中,按兩下表單介面,產生名為 Form1_Load 的 Form.Load 事件處理常式。
程式碼編輯器隨即出現,並將游標置於 Form1_Load 方法中。
將以下程式碼加入至 Form1_Load 方法中。
這段程式碼會以結束應用程式的方式,拒絕未經驗證之使用者的存取。 此外,您也可以允許未經驗證之使用者存取表單,但是拒絕其存取特定功能。 通常您不會以這種方式硬式編碼使用者名稱與密碼,但是基於測試目的就很有用。 在下一章節中,您將會使用更強固的程式碼取代這段程式碼,以顯示登入對話方塊並包含例外狀況處理。
請注意,static Membership.ValidateUser 方法是在 .NET Framework 2.0 版中。 這個方法會將其工作委派至已設定的驗證提供者,然後在驗證成功時傳回 true。 您的應用程式不需要直接參考用戶端驗證提供者。
If Not Membership.ValidateUser("manager", "manager!") Then MessageBox.Show("Unable to authenticate.", "Not logged in", _ MessageBoxButtons.OK, MessageBoxIcon.Error) Application.Exit() End If
if (!Membership.ValidateUser("manager", "manager!")) { MessageBox.Show("Unable to authenticate.", "Not logged in", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Exit(); }
您現在可以按 F5 執行應用程式,並且由於提供了正確的使用者名稱與密碼,您將會看到表單出現。
注意事項 |
---|
如果您無法執行應用程式,請嘗試停止 ASP.NET 程式開發伺服器。 當伺服器重新啟動時,請確認通訊埠是設定為 55555。 |
而若要看見錯誤訊息,請變更 ValidateUser 參數。 例如,以錯誤密碼取代第二個 "manager!" 參數,如 "MANAGER"。
加入登入表單當做認證提供者
您可以在應用程式程式碼中取得使用者認證,並將其傳遞至 ValidateUser 方法。 但是,通常將取得認證的程式碼與應用程式程式碼分開是很有用的,以方便後續想要予以變更。
在下列程序中,會將應用程式設定為使用認證提供者,然後變更您的 ValidateUser 方法呼叫,同時針對兩個參數傳遞 Empty。 空字串會對 ValidateUser 方法發出訊號,使其呼叫已設定之認證提供者的 GetCredentials 方法。
將應用程式設定為使用認證提供者
在 [方案總管] 中,選取 ClientAppServicesDemo 專案,然後在 [專案] 功能表上選取 [ClientAppServicesDemo 屬性]。
[專案設計工具] 隨即出現。
在 [服務] 索引標籤中,將 [選擇性:認證提供者] 設定為下列值。 這個值表示組件限定的類型名稱。
ClientAppServicesDemo.Login, ClientAppServicesDemo
在 Form1 程式碼檔案中,將 Form1_Load 方法中的程式碼以下列程式碼取代。
這段程式碼會顯示歡迎訊息,然後呼叫在下一步驟會加入的 ValidateUsingCredentialsProvider 方法。 如果使用者未經過驗證,ValidateUsingCredentialsProvider 方法會傳回 false 並且傳回 Form1_Load 方法。 這可以避免在應用程式結束之前執行任何其他的程式碼。 歡迎訊息可以清楚表示應用程式重新啟動的時間。 當您在這個逐步解說稍後實作登出時,將會加入程式碼以重新啟動應用程式。
MessageBox.Show("Welcome to the Client Application Services Demo.", _ "Welcome!") If Not ValidateUsingCredentialsProvider() Then Return
MessageBox.Show("Welcome to the Client Application Services Demo.", "Welcome!"); if (!ValidateUsingCredentialsProvider()) return;
將下列方法加到 Form1_Load 方法之後。
這個方法會將空字串傳遞至 static Membership.ValidateUser 方法,而出現 [登入] 對話方塊。 如果驗證服務無法使用,ValidateUser 方法將擲回 WebException。 在上述情形中,ValidateUsingCredentialsProvider 方法會顯示警告訊息,並詢問使用者是否要在離線模式再次嘗試。 這項功能需要在 HOW TO:設定用戶端應用程式服務中所描述的 [在本機儲存密碼雜湊以啟用離線登入] 功能。 新專案預設會啟用這項功能。
如果使用者並未經過驗證,ValidateUsingCredentialsProvider 方法會顯示錯誤訊息並結束應用程式。 最後,這個方法會傳回驗證嘗試的結果。
Private Function ValidateUsingCredentialsProvider() As Boolean Dim isAuthorized As Boolean = False Try ' Call ValidateUser with empty strings in order to display the ' login dialog box configured as a credentials provider. isAuthorized = Membership.ValidateUser( _ String.Empty, String.Empty) Catch ex As System.Net.WebException If DialogResult.OK = MessageBox.Show( _ "Unable to access the authentication service." & _ Environment.NewLine & "Attempt login in offline mode?", _ "Warning", MessageBoxButtons.OKCancel, _ MessageBoxIcon.Warning) Then ConnectivityStatus.IsOffline = True isAuthorized = Membership.ValidateUser( _ String.Empty, String.Empty) End If End Try If Not isAuthorized Then MessageBox.Show("Unable to authenticate.", "Not logged in", _ MessageBoxButtons.OK, MessageBoxIcon.Error) Application.Exit() End If Return isAuthorized End Function
private bool ValidateUsingCredentialsProvider() { bool isAuthorized = false; try { // Call ValidateUser with empty strings in order to display the // login dialog box configured as a credentials provider. isAuthorized = Membership.ValidateUser( String.Empty, String.Empty); } catch (System.Net.WebException) { if (DialogResult.OK == MessageBox.Show( "Unable to access the authentication service." + Environment.NewLine + "Attempt login in offline mode?", "Warning", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning)) { ConnectivityStatus.IsOffline = true; isAuthorized = Membership.ValidateUser( String.Empty, String.Empty); } } if (!isAuthorized) { MessageBox.Show("Unable to authenticate.", "Not logged in", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Exit(); } return isAuthorized; }
建立登入表單
認證提供者是實作 IClientFormsAuthenticationCredentialsProvider 介面的類別。 這個介面有個名為 GetCredentials 的單一方法,可傳回 ClientFormsAuthenticationCredentials 物件。 下列程序描述如何建立實作 GetCredentials 的登入對話方塊,以顯示本身並且傳回使用者指定的認證。
對 Visual Basic 和 C# 會提供不同的程序,因為 Visual Basic 會提供 [登入表單] 範本。 這可以節省一些時間與撰寫程式碼的人力。
建立登入對話方塊當做 Visual Basic 中的認證提供者
在 [方案總管] 中,選取 ClientAppServicesDemo 專案,然後在 [專案] 功能表上選取 [加入新項目]。
在 [加入新項目] 對話方塊中,選取 [登入表單] 範本,將項目 [名稱] 變更為 Login.vb,然後按一下 [加入]。
登入對話方塊就會出現在 [Windows Form 設計工具] 中。
在設計工具中,選取 [確定] 按鈕,然後在 [屬性] 視窗中將 DialogResult 設定為 OK。
在設計工具中,將 CheckBox 控制項加入至 [密碼] 文字方塊下的表單中。
在 [屬性] 視窗中,將 [(Name)] 值指定為 rememberMeCheckBox,並且將 [Text] 值指定為 &Remember me。
從 Visual Studio 主功能表選取 [檢視 | 程式碼]。
在程式碼編輯器中,將下列程式碼加入至檔案的頂端。
Imports System.Web.ClientServices.Providers
修改類別簽章以便讓類別實作 IClientFormsAuthenticationCredentialsProvider 介面。
Public Class Login Implements IClientFormsAuthenticationCredentialsProvider
請確定游標在 IClientformsAuthenticationCredentialsProvider 後方,然後按 ENTER 產生 GetCredentials 方法。
找到 GetCredentials 實作,然後以下列程式碼取代。
Public Function GetCredentials() As _ ClientFormsAuthenticationCredentials Implements _ IClientFormsAuthenticationCredentialsProvider.GetCredentials If Me.ShowDialog() = DialogResult.OK Then Return New ClientFormsAuthenticationCredentials( _ UsernameTextBox.Text, PasswordTextBox.Text, _ rememberMeCheckBox.Checked) Else Return Nothing End If End Function
下列 C# 程序提供簡單登入對話方塊的完整程式碼清單。 這個對話方塊的配置有一點粗糙,但是重點是 GetCredentials 實作。
建立登入對話方塊當做 C# 中的認證提供者
在 [方案總管] 中選取 ClientAppServicesDemo 專案,然後在 [專案] 功能表上選取 [加入類別]。
在 [加入新項目] 對話方塊中,將 [名稱] 變更為 Login.cs,然後按一下 [加入]。
Login.cs 檔會在程式碼編輯器中開啟。
請用下列程式碼取代預設程式碼。
using System.Windows.Forms; using System.Web.ClientServices.Providers; namespace ClientAppServicesDemo { class Login : Form, IClientFormsAuthenticationCredentialsProvider { private TextBox usernameTextBox; private TextBox passwordTextBox; private CheckBox rememberMeCheckBox; private Button OK; private Button cancel; private Label label1; private Label label2; public ClientFormsAuthenticationCredentials GetCredentials() { if (this.ShowDialog() == DialogResult.OK) { return new ClientFormsAuthenticationCredentials( usernameTextBox.Text, passwordTextBox.Text, rememberMeCheckBox.Checked); } else { return null; } } public Login() { InitializeComponent(); } private void CloseForm(object sender, System.EventArgs e) { this.Close(); } private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.usernameTextBox = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.passwordTextBox = new System.Windows.Forms.TextBox(); this.rememberMeCheckBox = new System.Windows.Forms.CheckBox(); this.OK = new System.Windows.Forms.Button(); this.cancel = new System.Windows.Forms.Button(); this.SuspendLayout(); // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(13, 13); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(58, 13); this.label1.TabIndex = 0; this.label1.Text = "&User name"; // // usernameTextBox // this.usernameTextBox.Location = new System.Drawing.Point(13, 30); this.usernameTextBox.Name = "usernameTextBox"; this.usernameTextBox.Size = new System.Drawing.Size(157, 20); this.usernameTextBox.TabIndex = 1; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(13, 57); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(53, 13); this.label2.TabIndex = 2; this.label2.Text = "&Password"; // // passwordTextBox // this.passwordTextBox.Location = new System.Drawing.Point(13, 74); this.passwordTextBox.Name = "passwordTextBox"; this.passwordTextBox.PasswordChar = '*'; this.passwordTextBox.Size = new System.Drawing.Size(157, 20); this.passwordTextBox.TabIndex = 3; // // rememberMeCheckBox // this.rememberMeCheckBox.AutoSize = true; this.rememberMeCheckBox.Location = new System.Drawing.Point(13, 101); this.rememberMeCheckBox.Name = "rememberMeCheckBox"; this.rememberMeCheckBox.Size = new System.Drawing.Size(94, 17); this.rememberMeCheckBox.TabIndex = 4; this.rememberMeCheckBox.Text = "&Remember me"; this.rememberMeCheckBox.UseVisualStyleBackColor = true; // // OK // this.OK.DialogResult = System.Windows.Forms.DialogResult.OK; this.OK.Location = new System.Drawing.Point(13, 125); this.OK.Name = "OK"; this.OK.Size = new System.Drawing.Size(75, 23); this.OK.TabIndex = 5; this.OK.Text = "&OK"; this.OK.UseVisualStyleBackColor = true; // // cancel // this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.cancel.Location = new System.Drawing.Point(95, 125); this.cancel.Name = "cancel"; this.cancel.Size = new System.Drawing.Size(75, 23); this.cancel.TabIndex = 6; this.cancel.Text = "&Cancel"; this.cancel.UseVisualStyleBackColor = true; // // Login // this.AcceptButton = this.OK; this.CancelButton = this.cancel; this.ClientSize = new System.Drawing.Size(187, 168); this.Controls.Add(this.cancel); this.Controls.Add(this.OK); this.Controls.Add(this.rememberMeCheckBox); this.Controls.Add(this.passwordTextBox); this.Controls.Add(this.label2); this.Controls.Add(this.usernameTextBox); this.Controls.Add(this.label1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "Login"; this.Text = "Login"; this.ResumeLayout(false); this.PerformLayout(); } } }
您現在可以執行應用程式,然後會看到登入對話方塊出現。 若要測試這段程式碼,請同時嘗試有效與無效的不同認證,然後確認您只能夠以有效認證存取表單。 有效的使用者名稱為 employee 和 manager,其密碼分別為 employee! 和 manager!。
注意事項 |
---|
請勿在此時選取 [儲存我的資訊],否則在此逐步解說稍後實作登出之前,您都將無法以其他使用者登入。 |
加入以角色為基礎的功能
在下列程序中,您會將按鈕加入表單並且只讓具有管理員角色的使用者檢視。
根據使用者角色變更使用者介面
在 [方案總管] 中的 ClientAppServicesDemo 專案中選取 Form1,然後選取 Visual Studio 主功能表中的 [檢視 | 設計工具]。
在設計工具中,從 [工具箱] 將 Button 控制項加入至表單。
在 [屬性] 視窗中,設定按鈕的下列屬性。
屬性
值
(名稱)
managerOnlyButton
Text
&Manager task
Visible
False
在 Form1 的程式碼編輯器中,將下列程式碼加入至 Form1_Load 方法的結尾。
這段程式碼會呼叫將在下一個步驟加入的 DisplayButtonForManagerRole 方法。
DisplayButtonForManagerRole()
DisplayButtonForManagerRole();
在 Form1 類別結尾處加入下列方法。
這個方法會呼叫 static Thread.CurrentPrincipal 屬性所傳回之 IPrincipal 的 IsInRole 方法。 針對設定為使用用戶端應用程式服務的應用程式,這個屬性會傳回 ClientRolePrincipal。 因為這個類別會實作 IPrincipal 介面,所以您不需要明確地參考它。
如果使用者具有 "manager" 角色,DisplayButtonForManagerRole 方法會將 managerOnlyButton 的 Visible 屬性設定為 true。 如果擲回 WebException 表示角色服務無法使用,這個方法也會顯示錯誤訊息。
注意事項 如果使用者登入已經逾期,IsInRole 方法就一定會傳回 false。 如果應用程式在驗證後很短的時間內呼叫一次 IsInRole 方法,如本逐步解說的範例程式碼所示,則不會發生這種情況。 如果應用程式必須在其他時間點擷取使用者角色,您就可能會想要加入程式碼,以重新驗證登入逾期的使用者。 如果所有有效的使用者都指派給角色,您可以呼叫 ClientRoleProvider.GetRolesForUser 方法以判斷登入是否過期。 如果沒有角色傳回,表示登入已經過期。 如需這項功能的範例,請參閱 GetRolesForUser 方法。 只有在應用程式組態中已選取 [要求使用者在伺服器 Cookie 過期時必須再次登入] 的情況下才需要這項功能。 如需詳細資訊,請參閱 HOW TO:設定用戶端應用程式服務。
Private Sub DisplayButtonForManagerRole() Try If Thread.CurrentPrincipal.IsInRole("manager") Then managerOnlyButton.Visible = True End If Catch ex As System.Net.WebException MessageBox.Show("Unable to access the roles service.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) End Try End Sub
private void DisplayButtonForManagerRole() { try { if (Thread.CurrentPrincipal.IsInRole("manager")) { managerOnlyButton.Visible = true; } } catch (System.Net.WebException) { MessageBox.Show("Unable to access the role service.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
如果驗證成功,用戶端驗證提供者會將 Thread.CurrentPrincipal 屬性設定為 ClientRolePrincipal 類別的執行個體。 這個類別會實作 IsInRole 方法,以便讓工作委派至已設定的角色提供者。 跟先前一樣,應用程式程式碼不需要直接參考服務提供者。
您現在可以執行應用程式並且以 employee 身分登入,您會發現按鈕並未顯示,然後使用 manager 身分登入則會看到按鈕。
存取 Web 設定
在下列程序中,您會將文字方塊加入至表單並繫結至 Web 設定。 如同先前使用驗證與角色的程式碼,您的設定程式碼不會直接存取設定提供者。 反而會使用 Visual Studio 針對專案所產生的強型別 Settings 類別 (在 C# 中是當做 Properties.Settings.Default 存取,而在 Visual Basic 中則是當做 My.Settings 存取)。
在使用者介面中使用 Web 設定
請檢查工作列的告知區域,以確定 [ASP.NET Web 程式開發伺服器] 仍在執行中。 如果您已經停止伺服器,請重新啟動應用程式 (這會自動啟動伺服器) 然後關閉登入對話方塊。
在 [方案總管] 中,選取 ClientAppServicesDemo 專案,然後在 [專案] 功能表上選取 [ClientAppServicesDemo 屬性]。
[專案設計工具] 隨即出現。
在 [設定] 索引標籤上,按一下 [載入 Web 設定]。
[登入] 對話方塊隨即出現。
輸入 employee 或 manager 的認證,然後按一下 [登入]。 您將使用的 Web 設定是設定為只有經過驗證的使用者才能存取,因此按一下 [略過登入] 將不會載入任何設定。
WebSettingsTestText 設定會以 DefaultText 的預設值出現在設計工具中。 此外,也會為專案產生包含了 WebSettingsTestText 屬性的 Settings 類別。
在 [方案總管] 中的 ClientAppServicesDemo 專案中選取 Form1,然後選取 Visual Studio 主功能表中的 [檢視 | 設計工具]。
在設計工具中,將 TextBox 控制項加入至表單。
在 [屬性] 視窗中,將 [(Name)] 值指定為 webSettingsTestTextBox。
在程式碼編輯器中,將下列程式碼加入至 Form1_Load 方法的結尾。
這段程式碼會呼叫將在下一個步驟加入的 BindWebSettingsTestTextBox 方法。
BindWebSettingsTestTextBox()
BindWebSettingsTestTextBox();
在 Form1 類別結尾處加入下列方法。
這個方法會將 webSettingsTestTextBox 的 Text 屬性,繫結至本程序稍早所產生之 Settings 類別的 WebSettingsTestText 屬性。 如果擲回 WebException 表示 Web 設定服務無法使用,這個方法也會顯示錯誤訊息。
Private Sub BindWebSettingsTestTextBox() Try Me.webSettingsTestTextBox.DataBindings.Add("Text", _ My.Settings, "WebSettingsTestText") Catch ex As WebException MessageBox.Show("Unable to access the Web settings service.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) End Try End Sub
private void BindWebSettingsTestTextBox() { try { this.webSettingsTestTextBox.DataBindings.Add("Text", Properties.Settings.Default, "WebSettingsTestText"); } catch (WebException) { MessageBox.Show("Unable to access the Web settings service.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
注意事項 您通常會使用資料繫結啟用控制項與 Web 設定之間的自動雙向通訊。 但是,您也可以直接存取 Web 設定,如同下列範例所示:
webSettingsTestTextBox.Text = My.Settings.WebSettingsTestText
webSettingsTestTextBox.Text = Properties.Settings.Default.WebSettingsTestText;
在設計工具中選取表單,然後在 [屬性] 視窗中按一下 [事件] 按鈕。
選取 FormClosing 事件,然後按 ENTER 產生事件處理常式。
以下列程式碼取代產生的方法。
FormClosing 事件處理常式會呼叫 SaveSettings 方法,而在下一章節所加入的登出功能也會使用這個方法。 SaveSettings 方法會先確定使用者尚未登出。 它會檢查目前主體所傳回之 IIdentity 的 AuthenticationType 屬性,以確定使用者尚未登出。 目前主體則是透過 static CurrentPrincipal 屬性所擷取。 如果使用者已經過驗證可使用用戶端應用程式服務,驗證類型將會是 "ClientForms"。 SaveSettings 方法不能只檢查 IIdentity.IsAuthenticated 屬性,因為使用者在登出後可能擁有有效的 Windows 識別。
如果使用者尚未登出,SaveSettings 方法會呼叫在此程序稍早所產生之 Settings 類別的 Save 方法。 如果驗證 Cookie 已逾期,這個方法會擲回 WebException。 只有在應用程式組態中已選取 [要求使用者在伺服器 Cookie 過期時必須再次登入] 才會發生這種情況。 如需詳細資訊,請參閱 HOW TO:設定用戶端應用程式服務。 SaveSettings 方法會透過呼叫 ValidateUser 顯示登入對話方塊的方式,處理 Cookie 逾期。 如果使用者成功登入,SaveSettings 方法會呼叫本身以嘗試再次儲存設定。
如同先前的程式碼,如果遠端服務無法使用,SaveSettings 方法就會顯示錯誤訊息。 如果設定提供者無法存取遠端服務,則設定仍然會儲存在本機快取中,然後在應用程式重新啟動時重新載入。
Private Sub Form1_FormClosing(ByVal sender As Object, _ ByVal e As FormClosingEventArgs) Handles Me.FormClosing SaveSettings() End Sub Private Sub SaveSettings() ' Return without saving if the authentication type is not ' "ClientForms". This indicates that the user is logged out. If Not Thread.CurrentPrincipal.Identity.AuthenticationType _ .Equals("ClientForms") Then Return Try My.Settings.Save() Catch ex As WebException If ex.Message.Contains("You must log on to call this method.") Then MessageBox.Show( _ "Your login has expired. Please log in again to save " & _ "your settings.", "Attempting to save settings...") Dim isAuthorized As Boolean = False Try ' Call ValidateUser with empty strings in order to ' display the login dialog box configured as a ' credentials provider. If Not Membership.ValidateUser( _ String.Empty, String.Empty) Then MessageBox.Show("Unable to authenticate. " & _ "Settings were not saved on the remote service.", _ "Not logged in", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else ' Try again. SaveSettings() End If Catch ex2 As System.Net.WebException MessageBox.Show( _ "Unable to access the authentication service. " & _ "Settings were not saved on the remote service.", _ "Not logged in", MessageBoxButtons.OK, _ MessageBoxIcon.Warning) End Try Else MessageBox.Show("Unable to access the Web settings service. " & _ "Settings were not saved on the remote service.", _ "Not logged in", MessageBoxButtons.OK, _ MessageBoxIcon.Warning) End If End Try End Sub
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { SaveSettings(); } private void SaveSettings() { // Return without saving if the authentication type is not // "ClientForms". This indicates that the user is logged out. if (!Thread.CurrentPrincipal.Identity.AuthenticationType .Equals("ClientForms")) return; try { Properties.Settings.Default.Save(); } catch (WebException ex) { if (ex.Message.Contains("You must log on to call this method.")) { MessageBox.Show( "Your login has expired. Please log in again to save " + "your settings.", "Attempting to save settings..."); try { // Call ValidateUser with empty strings in order to // display the login dialog box configured as a // credentials provider. if (!Membership.ValidateUser(String.Empty, String.Empty)) { MessageBox.Show("Unable to authenticate. " + "Settings were not saved on the remote service.", "Not logged in", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // Try again. SaveSettings(); } } catch (System.Net.WebException) { MessageBox.Show( "Unable to access the authentication service. " + "Settings were not saved on the remote service.", "Not logged in", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } else { MessageBox.Show("Unable to access the Web settings service. " + "Settings were not saved on the remote service.", "Not logged in", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } }
在 Form1 類別結尾處加入下列方法。
如果有任何設定無法儲存,這段程式碼會處理 ClientSettingsProvider.SettingsSaved 事件並顯示警告。 如果設定服務無法使用或是驗證 Cookie 已經逾期,SettingsSaved 事件就不會發生。 SettingsSaved 事件發生時機的其中一個範例是如果使用者已經登出。 您可以在 Save 方法呼叫之前,將登出程式碼直接加入至 SaveSettings 方法以測試這個事件處理常式。 您可以使用的登出程式碼會在下一個章節中描述。
Private WithEvents settingsProvider As ClientSettingsProvider = My.Settings _ .Providers("System.Web.ClientServices.Providers.ClientSettingsProvider") Private Sub Form1_SettingsSaved(ByVal sender As Object, _ ByVal e As SettingsSavedEventArgs) _ Handles settingsProvider.SettingsSaved ' If any settings were not saved, display a list of them. If e.FailedSettingsList.Count > 0 Then Dim failedSettings As String = String.Join( _ Environment.NewLine, e.FailedSettingsList.ToArray()) Dim message As String = String.Format("{0}{1}{1}{2}", _ "The following setting(s) were not saved:", _ Environment.NewLine, failedSettings) MessageBox.Show(message, "Unable to save settings", _ MessageBoxButtons.OK, MessageBoxIcon.Warning) End If End Sub
private void Form1_SettingsSaved(object sender, SettingsSavedEventArgs e) { // If any settings were not saved, display a list of them. if (e.FailedSettingsList.Count > 0) { String failedSettings = String.Join( Environment.NewLine, e.FailedSettingsList.ToArray()); String message = String.Format("{0}{1}{1}{2}", "The following setting(s) were not saved:", Environment.NewLine, failedSettings); MessageBox.Show(message, "Unable to save settings", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
如果是使用 C#,請將下列程式碼加入至 Form1_Load 方法的結尾。 這個程式碼會讓在上一步驟中加入的方法與 SettingsSaved 事件產生關聯。
((ClientSettingsProvider)Properties.Settings.Default.Providers ["System.Web.ClientServices.Providers.ClientSettingsProvider"]) .SettingsSaved += new EventHandler<SettingsSavedEventArgs>(Form1_SettingsSaved);
若要在此時測試應用程式,請同時使用 employee 和 manager 身分執行數次,並在文字方塊中輸入不同的值。 這個值將會以每個使用者為基礎保存在所有工作階段中。
實作登出
當使用者在登入時選取 [儲存我的資訊] 核取方塊時,應用程式在後續執行時就會自動驗證使用者。 當應用程式在離線模式時,自動驗證仍然會繼續執行直到驗證 Cookie 逾期為止。 但是,有時候多個使用者會需要存取應用程式,或是單一使用者可能會偶爾使用不同的認證登入。 若要啟用這類案例,您必須實作登出功能,如同下列程序所述。
實作登出功能
在 Form1 設計工具中,從 [工具箱] 將 Button 控制項加入至表單。
在 [屬性] 視窗中,將 [(Name)] 的值指定為 logoutButton,並且將 [Text] 的值指定為 &Log Out。
按兩下 [logoutButton] 產生 Click 事件處理常式。
程式碼編輯器隨即出現,並將游標置於 logoutButton_Click 方法中。
以下列程式碼取代產生的 logoutButton_Click 方法。
這個事件處理常式會先呼叫在先前章節中加入的 SaveSettings 方法。 然後事件處理常式會呼叫 ClientFormsAuthenticationMembershipProvider.Logout 方法。 如果驗證服務無法使用,Logout 方法將擲回 WebException。 在上述情形中,logoutButton_Click 方法會顯示警告訊息,並且暫時切換至離線模式將使用者登出。 下一章節會描述離線模式。
登出會刪除本機驗證 Cookie,因此當重新啟動應用程式時就需要登入。 在登出後,事件處理常式就會重新啟動應用程式。 應用程式重新啟動時,會在歡迎訊息後顯示登入對話方塊。 歡迎訊息可以清楚表示應用程式已重新啟動。 如果使用者必須登入才能儲存設定,然後因為應用程式重新啟動而必須再登入一次時,如此可以避免可能的混淆情況。
Private Sub logoutButton_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles logoutButton.Click SaveSettings() Dim authProvider As ClientFormsAuthenticationMembershipProvider = _ CType(System.Web.Security.Membership.Provider, _ ClientFormsAuthenticationMembershipProvider) Try authProvider.Logout() Catch ex As WebException MessageBox.Show("Unable to access the authentication service." & _ Environment.NewLine & "Logging off locally only.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) ConnectivityStatus.IsOffline = True authProvider.Logout() ConnectivityStatus.IsOffline = False End Try Application.Restart() End Sub
private void logoutButton_Click(object sender, EventArgs e) { SaveSettings(); ClientFormsAuthenticationMembershipProvider authProvider = (ClientFormsAuthenticationMembershipProvider) System.Web.Security.Membership.Provider; try { authProvider.Logout(); } catch (WebException ex) { MessageBox.Show("Unable to access the authentication service." + Environment.NewLine + "Logging off locally only.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); ConnectivityStatus.IsOffline = true; authProvider.Logout(); ConnectivityStatus.IsOffline = false; } Application.Restart(); }
若要測試登出功能,請執行應用程式然後在 [登入] 對話方塊上選取 [儲存我的資訊]。 然後關閉再重新啟動應用程式以確認不再需要登入。 最後,按一下 [登出] 重新啟動應用程式。
啟用離線模式
在下列程序中,會將核取方塊加入至表單,讓使用者能夠進入離線模式。 將 static ConnectivityStatus.IsOffline 屬性設定為 true,表示應用程式為離線模式。 離線狀態會儲存在 Application.UserAppDataPath 屬性所指定的本機硬碟位置中。 這表示離線狀態是以個別使用者以及應用程式為基礎儲存。
在離線模式中,所有的用戶端應用程式服務要求會從本機快取擷取資料,而不是嘗試存取服務。 在預設組態中,本機資料包含以加密格式儲存的使用者密碼。 這可以讓使用者在應用程式處於離線模式時仍能登入。 如需詳細資訊,請參閱 HOW TO:設定用戶端應用程式服務。
在應用程式中啟用離線模式
在 [方案總管] 中的 ClientAppServicesDemo 專案中選取 Form1,然後選取 Visual Studio 主功能表中的 [檢視 | 設計工具]。
在設計工具中,將 CheckBox 控制項加入至表單。
在 [屬性] 視窗中,將 [(Name)] 的值指定為 workOfflineCheckBox,並且將 [Text] 的值指定為 &Work offline。
在 [屬性] 視窗中,按一下 [事件] 按鈕。
選取 CheckedChanged 事件,然後按 ENTER 產生事件處理常式。
以下列程式碼取代產生的方法。
這段程式碼會更新 IsOffline 值,然後在返回線上模式時以無訊息模式重新驗證使用者。 ClientFormsIdentity.RevalidateUser 方法會使用已快取的認證,所以使用者不需要明確地進行登入動作。 如果驗證服務無法使用,就會出現警告訊息,而應用程式仍然保持離線狀態。
注意事項 RevalidateUser 方法只是為了方便而使用。 因為它沒有傳回值,因此無法表示重新驗證是否失敗。 例如,如果在伺服器上的使用者認證已變更,重新驗證就可能會失敗。 在這種情況中,您可能想要加入在服務呼叫失敗之後能夠明確驗證使用者的程式碼。 如需詳細資訊,請參閱此逐步解說中的「存取 Web 設定」章節。
在重新驗證後,這段程式碼會呼叫先前加入的 SaveSettings 方法,將任何變更儲存在本機 Web 設定中。 然後呼叫專案 Settings 類別 (在 C# 中當做 Properties.Settings.Default 存取,而在 Visual Basic 中當做 My.Settings 存取) 的 Reload 方法,擷取伺服器上任何新增的值。
Private Sub workOfflineCheckBox_CheckedChanged( _ ByVal sender As Object, ByVal e As EventArgs) _ Handles workOfflineCheckBox.CheckedChanged ConnectivityStatus.IsOffline = workOfflineCheckBox.Checked If Not ConnectivityStatus.IsOffline Then Try ' Silently re-validate the user. CType(System.Threading.Thread.CurrentPrincipal.Identity, _ ClientFormsIdentity).RevalidateUser() ' If any settings have been changed locally, save the new ' new values to the Web settings service. SaveSettings() ' If any settings have not been changed locally, check ' the Web settings service for updates. My.Settings.Reload() Catch ex As WebException MessageBox.Show( _ "Unable to access the authentication service. " & _ Environment.NewLine + "Staying in offline mode.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) workOfflineCheckBox.Checked = True End Try End If End Sub
private void workOfflineCheckBox_CheckedChanged( object sender, EventArgs e) { ConnectivityStatus.IsOffline = workOfflineCheckBox.Checked; if (!ConnectivityStatus.IsOffline) { try { // Silently re-validate the user. ((ClientFormsIdentity) System.Threading.Thread.CurrentPrincipal.Identity) .RevalidateUser(); // If any settings have been changed locally, save the new // new values to the Web settings service. SaveSettings(); // If any settings have not been changed locally, check // the Web settings service for updates. Properties.Settings.Default.Reload(); } catch (WebException) { MessageBox.Show( "Unable to access the authentication service. " + Environment.NewLine + "Staying in offline mode.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); workOfflineCheckBox.Checked = true; } } }
將下列程式碼加入至 Form1_Load 方法的結尾,以確定核取方塊會顯示目前的連接狀態。
workOfflineCheckBox.Checked = ConnectivityStatus.IsOffline
workOfflineCheckBox.Checked = ConnectivityStatus.IsOffline;
如此完成了範例應用程式。 若要測試離線功能,請執行應用程式、使用 employee 或 manager 的身分登入,然後選取 [離線工作]。 修改文字方塊中的值,然後關閉應用程式 再重新啟動。 在登入前,以滑鼠右鍵按一下工作列上告知區域中的 ASP.NET 程式開發伺服器圖示,然後按一下 [停止]。 然後以正常的方式登入。 即使伺服器並未在執行中,您仍然可以登入。 請修改文字方塊的值、結束然後重新啟動,以檢視修改過的值。
摘要
在這個逐步解說中,您已學習到如何在 Windows Form 應用程式中啟用與使用用戶端應用程式服務。 在設定測試伺服器後,您可以將程式碼加入至應用程式驗證使用者,並且從伺服器擷取使用者角色和應用程式設定。 您也學習了如何啟用離線模式,以便讓應用程式在無法連接時,使用本機資料快取而不是遠端服務。
後續步驟
現實的應用程式會從遠端伺服器存取許多使用者的資料,而遠端伺服器並非隨時都能使用,或是可能會在未告知的情況下關機。 若要讓應用程式更強固,就必須在服務無法使用時適當回應各種情況。 這個逐步解說包含 try/catch 區塊攔截 WebException,然後在服務無法使用時顯示錯誤訊息。 在實際執行的程式碼中,您可能想要藉由切換至離線模式、結束應用程式,或拒絕存取特定功能的方式處理這種情況。
若要增加應用程式的安全性,請確定在部署之前已徹底的測試應用程式與伺服器。