Walkthrough: Using Client Application Services
This topic describes how to create a Windows application that uses client application services to authenticate users and retrieve user roles and settings.
In this walkthrough, you perform the following tasks:
Create a Windows Forms application and use the Visual Studio project designer to enable and configure client application services.
Create a simple ASP.NET Web Service application to host the application services and test your client configuration.
Add forms authentication to your application. You will start by using a hard-coded user name and password to test the service. You will then add a login form by specifying it as a credentials provider in your application configuration.
Add role-based functionality, enabling and displaying a button only for users in the "manager" role.
Access Web settings. You will start by loading Web settings for an authenticated (test) user on the Settings page of the project designer. You will then use the Windows Forms Designer to bind a text box to a Web setting. Finally, you will save the modified value back to the server.
Implement logout. You will add a logout option to the form and call a logout method.
Enable offline mode. You will provide a check box so that users can specify their connection status. You will then use this value to specify whether the client application service providers will use locally cached data instead of accessing their Web services. Finally, you will re-authenticate the current user when the application returns to online mode.
Prerequisites
You need the following component to complete this walkthrough:
- Visual Studio 2008.
Creating the Client Application
The first thing that you will do is create a Windows Forms project. This walkthrough uses Windows Forms because more people are familiar with it, but the process is similar for Windows Presentation Foundation (WPF) projects.
To create a client application and enable client application services
In Visual Studio, select the File | New | Project menu option.
In the New Project dialog box, in the Project types pane, expand the Visual Basic or Visual C# node and select the Windows project type.
Make sure that .NET Framework 3.5 is selected, and then select the Windows Forms Application template.
Change the project Name to ClientAppServicesDemo, and then click OK.
A new Windows Forms project is opened in Visual Studio.
On the Project menu, select ClientAppServicesDemo Properties.
The project designer appears.
On the Services tab, select Enable client application services.
Make sure that Use Forms authentication is selected, and then set Authentication service location, Roles service location, and Web settings service location to https://localhost:55555/AppServices.
For Visual Basic, on the Application tab, set Authentication mode to Application-defined.
The designer stores the specified settings in the application's app.config file.
At this point, the application is configured to access all three services from the same host. In the next section, you will create the host as a simple Web service application, enabling you to test your client configuration.
Creating the Application Services Host
In this section, you will create a simple Web service application that accesses user data from a local SQL Server Compact 3.5 database file. Then, you will populate the database using the ASP.NET Web Site Administration Tool. This simple configuration enables you to quickly test your client application. As an alternative, you can configure the Web service host to access user data from a full SQL Server database or through custom MembershipProvider and RoleProvider classes. For more information, see Creating and Configuring the Application Services Database for SQL Server.
In the following procedure, you create and configure the AppServices Web service.
To create and configure the application services host
In Solution Explorer, select the ClientAppServicesDemo solution, and then on the File menu, select Add | New Project.
In the Add New Project dialog box, in the Project types pane, expand the Visual Basic or Visual C# node and select the Web project type.
Make sure that .NET Framework 3.5 is selected, and then select the ASP.NET Web Service Application template.
Change the project Name to AppServices and then click OK.
A new ASP.NET Web service application project is added to the solution, and the Service1.asmx.vb or Service1.asmx.cs file appears in the editor.
Note
The Service1.asmx.vb or Service1.asmx.cs file is not used in this example. If you want to keep your work environment uncluttered, you can close it and delete it from Solution Explorer.
In Solution Explorer, select the AppServices project, and then on the Project menu, select AppServices Properties.
The project designer appears.
On the Web tab, make sure that Use Visual Studio Development Server is selected.
Select Specific port, specify a value of 55555, and then set Virtual path to /AppServices.
Save all files.
In Solution Explorer, open Web.config and find the <system.web> opening tag.
Add the following markup before the <system.web> tag.
The authenticationService, profileService, and roleService elements in this markup enable and configure the application services. For testing purposes, the requireSSL attribute of the authenticationService element is set to "false". The readAccessProperties and writeAccessProperties attributes of the profileService element indicate that the WebSettingsTestText property is read/write.
Note
In production code, you should always access the authentication service over the secure sockets layer (SSL, by using the HTTPS protocol). For information about how to set up SSL, see Configuring Secure Sockets Layer (IIS 6.0 Operations Guide).
<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>
Add the following markup after the <system.web> opening tag so that it is within the <system.web> element.
The profile element configures a single Web setting named WebSettingsTestText.
<profile enabled="true" > <properties> <add name="WebSettingsTestText" type="string" readOnly="false" defaultValue="DefaultText" serializeAs="String" allowAnonymous="false" /> </properties> </profile>
In the following procedure, you use the ASP.NET Web Site Administration tool to complete the service configuration and populate the local database file. You will add two users named employee and manager belonging to two roles with the same names. The user passwords are employee! and manager! respectively.
To configure membership and roles
In Solution Explorer, select the AppServices project, and then on the Project menu, select ASP.NET Configuration.
The ASP.NET Web Site Administration Tool appears.
On the Security tab, click Use the security Setup Wizard to configure security step by step.
The Security Setup Wizard appears and displays the Welcome step.
Click Next.
The Select Access Method step appears.
Select From the internet. This configures the service to use Forms authentication instead of Windows authentication.
Click Next twice.
The Define Roles step appears.
Select Enable roles for this Web site.
Click Next. The Create New Role form appears.
In the New Role Name text box, type manager and then click Add Role.
The Existing Roles table appears with the specified value.
In the New Role Name text box, replace manager with employee and then click Add Role.
The new value appears in the Existing Roles table.
Click Next.
The Add New Users step appears.
In the Create User form, specify the following values.
User Name
manager
Password
manager!
Confirm Password
manager!
E-mail
Security Question
manager
Security Answer
manager
Click Create User.
A success message appears.
Note
The E-mail, Security Question, and Security Answer values are required by the form, but are not used in this example.
Click Continue.
The Create User form reappears.
In the Create User form, specify the following values.
User Name
employee
Password
employee!
Confirm Password
employee!
E-mail
Security Question
Employee
Security Answer
employee
Click Create User.
A success message appears.
Click Finish.
The Web Site Administration Tool reappears.
Click Manager users.
The list of users appears.
Click Edit roles for the employee user, and then select the employee role.
Click Edit roles for the manager user, and then select the manager role.
Close the browser window that hosts the Web Site Administration Tool.
If a message box appears asking if you want to reload the modified Web.config file, click Yes.
This completes the Web service setup. At this point, you can press F5 to run the client application, and the ASP.NET Development Server will start automatically along with your client application. The server will continue to run after you exit the application, but will restart when you restart the application. This allows it to detect any changes you have made to Web.config.
To stop the server manually, right-click the ASP.NET Development Server icon in the notification area of the taskbar and then click Stop. This is useful occasionally to make sure that a clean restart occurs.
Adding Forms Authentication
In the following procedure, you add code to the main form that attempts to validate the user, and denies access when the user supplies invalid credentials. You use a hard-coded user name and password to test the service.
To validate the user in your application code
In Solution Explorer, in the ClientAppServicesDemo project, add a reference to the System.Web assembly.
Select the Form1 file and then select View | Code from the Visual Studio main menu.
In the code editor, add the following statements to the top of the Form1 file.
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;
In Solution Explorer, double-click Form1 to display the designer.
In the designer, double-click the form surface to generate a Form.Load event handler named Form1_Load.
The code editor appears with the cursor in the Form1_Load method.
Add the following code to the Form1_Load method.
This code denies access to unauthenticated users by exiting the application. Alternatively, you could allow unauthenticated users access to the form, but deny access to specific functionality. Normally, you will not hard-code the user name and password like this, but it is useful for testing purposes. In the next section, you will replace this code with more robust code that displays a login dialog box and includes exception handling.
Note that the static Membership.ValidateUser method is in the .NET Framework version 2.0. This method delegates its work to the configured authentication provider, and returns true if authentication is successful. Your application does not require a direct reference to the client authentication provider.
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(); }
You can now press F5 to run the application, and because you provide a correct user name and password, you will see the form.
Note
If you are unable to run the application, try stopping the ASP.NET Development Server. When the server restarts, verify that the port is set to 55555.
To see the error message instead, change the ValidateUser parameters. For example, replace the second "manager!" parameter with an incorrect password like "MANAGER".
Adding a Login Form as a Credentials Provider
You can acquire the user credentials in your application code and pass them to the ValidateUser method. However, it is often useful to keep your credentials-acquiring code separate from your application code, in case you want to change it later.
In the following procedure, you configure your application to use a credentials provider, and then change your ValidateUser method call to pass Empty for both parameters. The empty strings signal the ValidateUser method to call the GetCredentials method of the configured credentials provider.
To configure your application to use a credentials provider
In Solution Explorer, select the ClientAppServicesDemo project, and then on the Project menu, select ClientAppServicesDemo Properties.
The project designer appears.
On the Services tab, set Optional: Credential provider to the following value. This value indicates the assembly-qualified type name.
ClientAppServicesDemo.Login, ClientAppServicesDemo
In the Form1 code file, replace the code in the Form1_Load method with the following code.
This code displays a welcome message and then calls the ValidateUsingCredentialsProvider method that you will add in the next step. If the user is not authenticated, the ValidateUsingCredentialsProvider method returns false and the Form1_Load method returns. This prevents any additional code from running before the application exits. The welcome message is useful to make it clear when the application restarts. You will add code to restart the application when you implement logout later in this walkthrough.
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;
Add the following method after the Form1_Load method.
This method passes empty strings to the static Membership.ValidateUser method, which causes the Login dialog box to appear. If the authentication service is unavailable, the ValidateUser method will throw a WebException. In this case, the ValidateUsingCredentialsProvider method displays a warning message and asks if the user wants to try again in offline mode. This functionality requires the Save password hash locally to enable offline login feature described in How to: Configure Client Application Services. This feature is enabled by default for new projects.
If the user is not validated, the ValidateUsingCredentialsProvider method displays an error message and exits the application. Finally, this method returns the result of the authentication attempt.
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; }
Creating a Login Form
A credentials provider is a class that implements the IClientFormsAuthenticationCredentialsProvider interface. This interface has a single method named GetCredentials that returns a ClientFormsAuthenticationCredentials object. The following procedures describe how to create a login dialog box that implements GetCredentials to display itself and return the user-specified credentials.
Separate procedures are provided for Visual Basic and C# because Visual Basic provides a Login Form template. This saves some time and coding effort.
To create a login dialog box as a credentials provider in Visual Basic
In Solution Explorer, select the ClientAppServicesDemo project, and then on the Project menu, select Add New Item.
In the Add New Item dialog box, select the Login Form template, change the item Name to Login.vb, and then click Add.
The login dialog box appears in the Windows Forms Designer.
In the designer, select the OK button and then, in the Properties window, set DialogResult to OK.
In the designer, add a CheckBox control to the form under the Password text box.
In the Properties window, specify a (Name) value of rememberMeCheckBox and a Text value of &Remember me.
Select View | Code from the Visual Studio main menu.
In the code editor, add the following code to the top of the file.
Imports System.Web.ClientServices.Providers
Modify the class signature so that the class implements the IClientFormsAuthenticationCredentialsProvider interface.
Public Class Login Implements IClientFormsAuthenticationCredentialsProvider
Make sure that the cursor is after IClientformsAuthenticationCredentialsProvider, and then press ENTER to generate the GetCredentials method.
Locate the GetCredentials implementation and then replace it with the following code.
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
The following C# procedure provides the entire code listing for a simple login dialog box. The layout for this dialog box is a bit crude, but the important part is the GetCredentials implementation.
To create a login dialog box as a credentials provider in C#
In Solution Explorer, select the ClientAppServicesDemo project, and then on the Project menu, select Add Class.
In the Add New Item dialog box, change the Name to Login.cs, and then click Add.
The Login.cs file opens in the code editor.
Replace the default code with the following code.
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(); } } }
You can now run the application and see the login dialog box appear. To test this code, try different credentials, both valid and invalid, and confirm that you can access the form only with valid credentials. Valid user names are employee and manager with passwords employee! and manager! respectively.
Note
Do not select Remember me at this point or you will not be able to login as another user until you implement logout later in this walkthrough.
Adding Role-Based Functionality
In the following procedure, you add a button to the form and display it only for users in the manager role.
To change the user interface based on user role
In Solution Explorer, in the ClientAppServicesDemo project, select Form1 and then select View | Designer from the Visual Studio main menu.
In the designer, add a Button control to the form from the ToolBox.
In the Properties window, set the following properties for the button.
Property
Value
(Name)
managerOnlyButton
Text
&Manager task
Visible
False
In the code editor for Form1, add the following code to the end of the Form1_Load method.
This code calls the DisplayButtonForManagerRole method that you will add in the next step.
DisplayButtonForManagerRole()
DisplayButtonForManagerRole();
Add the following method to the end of the Form1 class.
This method calls the IsInRole method of the IPrincipal returned by the static Thread.CurrentPrincipal property. For applications configured to use client application services, this property returns a ClientRolePrincipal. Because this class implements the IPrincipal interface, you do not need to reference it explicitly.
If the user is in the "manager" role, the DisplayButtonForManagerRole method sets the Visible property of the managerOnlyButton to true. This method also displays an error message if a WebException is thrown, which indicates that the roles service is unavailable.
Note
The IsInRole method will always return false if the user login has expired. This will not occur if your application calls the IsInRole method one time shortly after authentication, as shown in the example code for this walkthrough. If your application must retrieve user roles at other times, you might want to add code to revalidate users whose login has expired. If all valid users are assigned to roles, you can determine whether the login has expired by calling the ClientRoleProvider.GetRolesForUser method. If no roles are returned, the login has expired. For an example of this functionality, see the GetRolesForUser method. This functionality is only necessary if you have selected Require users to log on again whenever the server cookie expires in your application configuration. For more information, see How to: Configure Client Application Services.
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); } }
If authentication is successful, the client authentication provider sets the Thread.CurrentPrincipal property to an instance of the ClientRolePrincipal class. This class implements the IsInRole method so that the work is delegated to the configured role provider. As before, your application code does not require a direct reference to the service provider.
You can now run the application and log in as employee to see that the button does not appear, and then log in as manager to see the button.
Accessing Web Settings
In the following procedure, you add a text box to the form and bind it to a Web setting. Like the previous code that uses authentication and roles, your settings code does not access the settings provider directly. Instead, it uses the strongly-typed Settings class (accessed as Properties.Settings.Default in C# and My.Settings in Visual Basic) generated for your project by Visual Studio.
To use Web settings in your user interface
Make sure that the ASP.NET Web Development Server is still running by checking the notification area of the taskbar. If you have stopped the server, restart the application (which starts the server automatically) then close the login dialog box.
In Solution Explorer, select the ClientAppServicesDemo project, and then on the Project menu, select ClientAppServicesDemo Properties.
The project designer appears.
On the Settings tab, click Load Web Settings.
A Login dialog box appears.
Enter credentials for employee or manager and click Log In. The Web setting you will use is configured for access by authenticated users only, so clicking Skip Login will not load any settings.
The WebSettingsTestText setting appears in the designer with the default value of DefaultText. Additionally, a Settings class that contains a WebSettingsTestText property is generated for your project.
In Solution Explorer, in the ClientAppServicesDemo project, select Form1 and then select View | Designer from the Visual Studio main menu.
In the designer, add a TextBox control to the form.
In the Properties window, specify a (Name) value of webSettingsTestTextBox.
In the code editor, add the following code to the end of the Form1_Load method.
This code calls the BindWebSettingsTestTextBox method that you will add in the next step.
BindWebSettingsTestTextBox()
BindWebSettingsTestTextBox();
Add the following method to the end of the Form1 class.
This method binds the Text property of the webSettingsTestTextBox to the WebSettingsTestText property of the Settings class generated earlier in this procedure. This method also displays an error message if a WebException is thrown, which indicates that the Web settings service is unavailable.
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); } }
Note
You will typically use data binding to enable automatic two-way communication between a control and a Web setting. However, you can also access Web settings directly as shown in the following example:
webSettingsTestTextBox.Text = My.Settings.WebSettingsTestText
webSettingsTestTextBox.Text = Properties.Settings.Default.WebSettingsTestText;
In the designer, select the form, and then, in the Properties window, click the Events button.
Select the FormClosing event and then press ENTER to generate an event handler.
Replace the generated method with the following code.
The FormClosing event handler calls the SaveSettings method, which is also used by the logout functionality that you will add in the next section. The SaveSettings method first confirms that the user has not logged out. It does this by checking the AuthenticationType property of the IIdentity returned by the current principal. The current principal is retrieved through the static CurrentPrincipal property. If the user has been authenticated for client application services, the authentication type will be "ClientForms". The SaveSettings method cannot just check the IIdentity.IsAuthenticated property because the user might have a valid Windows identity after logout.
If the user has not logged out, the SaveSettings method calls the Save method of the Settings class generated earlier in this procedure. This method can throw a WebException if the authentication cookie has expired. This occurs only if you have selected Require users to log on again whenever the server cookie expires in your application configuration. For more information, see How to: Configure Client Application Services. The SaveSettings method handles cookie expiration by calling ValidateUser to display the login dialog box. If the user logs in successfully, the SaveSettings method tries to save the settings again by calling itself.
Like in previous code, the SaveSettings method displays an error message if the remote service is unavailable. If the settings provider cannot access the remote service, the settings are still saved to the local cache and reloaded when the application restarts.
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); } } }
Add the following method to the end of the Form1 class.
This code handles the ClientSettingsProvider.SettingsSaved event and displays a warning if any of the settings could not be saved. The SettingsSaved event does not occur if the settings service is unavailable or if the authentication cookie has expired. One example of when the SettingsSaved event will occur is if the user has already logged out. You can test this event handler by adding logout code to the SaveSettings method directly before the Save method call. Logout code that you can use is described in the next section.
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); } }
For C#, add the following code to the end of the Form1_Load method. This code associates the method you added in the last step with the SettingsSaved event.
((ClientSettingsProvider)Properties.Settings.Default.Providers ["System.Web.ClientServices.Providers.ClientSettingsProvider"]) .SettingsSaved += new EventHandler<SettingsSavedEventArgs>(Form1_SettingsSaved);
To test the application at this point, run it multiple times as both employee and manager, and type different values into the text box. The values will persist across sessions on a per-user basis.
Implementing Logout
When the user selects the Remember me check box when logging in, the application will automatically authenticate the user on subsequent runs. Automatic authentication will then continue while the application is in offline mode or until the authentication cookie expires. Sometimes, however, multiple users will need access to the application or a single user might occasionally log in with different credentials. To enable this scenario, you must implement logout functionality, as described in the following procedure.
To implement logout functionality
In the Form1 designer, add a Button control to the form from the ToolBox.
In the Properties window, specify a (Name) value of logoutButton and a Text value of &Log Out.
Double-click the logoutButton to generate a Click event handler.
The code editor appears with the cursor in the logoutButton_Click method.
Replace the generated logoutButton_Click method with the following code.
This event handler first calls the SaveSettings method that you added in the previous section. Then, the event handler calls the ClientFormsAuthenticationMembershipProvider.Logout method. If the authentication service is unavailable, the Logout method will throw a WebException. In this case, the logoutButton_Click method displays a warning message and switches temporarily to offline mode to log the user out. Offline mode is described in the next section.
Logout deletes the local authentication cookie so that login will be required when the application is restarted. After logout, the event handler restarts the application. When the application restarts, it displays the welcome message followed by the login dialog box. The welcome message makes it clear that the application has restarted. This prevents potential confusion if the user must log in to save settings, and then must log in again because the application has restarted.
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(); }
To test the logout functionality, run the application and select Remember me on the Login dialog box. Next, close and restart the application to confirm that you no longer have to log in. Finally, restart the application by clicking Log out.
Enabling Offline Mode
In the following procedure, you add a check box to the form to enable the user to enter offline mode. Your application indicates offline mode by setting the static ConnectivityStatus.IsOffline property to true. The offline status is stored on the local hard disk at the location indicated by the Application.UserAppDataPath property. This means that the offline status is stored on a per-user, per-application basis.
In offline mode, all client application service requests retrieve data from the local cache instead of trying to access the services. In the default configuration, the local data includes an encrypted form of the user's password. This enables the user to log in while the application is in offline mode. For more information, see How to: Configure Client Application Services.
To enable offline mode in your application
In Solution Explorer, in the ClientAppServicesDemo project, select Form1 and then select View | Designer from the Visual Studio main menu.
In the designer, add a CheckBox control to the form.
In the Properties window, specify a (Name) value of workOfflineCheckBox and a Text value of &Work offline.
In the Properties window, click the Events button.
Select the CheckedChanged event and then press ENTER to generate an event handler.
Replace the generated method with the following code.
This code updates the IsOffline value and silently revalidates the user when they return to online mode. The ClientFormsIdentity.RevalidateUser method uses the cached credentials so that the user does not have to explicitly log in. If the authentication service is unavailable, a warning message appears and the application stays offline.
Note
The RevalidateUser method is for convenience only. Because it does not have a return value, it cannot indicate whether revalidation has failed. Revalidation can fail, for example, if the user credentials have changed on the server. In this case, you might want to include code that explicitly validates users after a service call fails. For more information, see the Accessing Web Settings section earlier in this walkthrough.
After revalidation, this code saves any changes to the local Web settings by calling the SaveSettings method that you added previously. It then retrieves any new values on the server by calling the Reload method of the project's Settings class (accessed as Properties.Settings.Default in C# and My.Settings in Visual Basic).
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; } } }
Add the following code to the end of the Form1_Load method to make sure that the check box displays the current connection state.
workOfflineCheckBox.Checked = ConnectivityStatus.IsOffline
workOfflineCheckBox.Checked = ConnectivityStatus.IsOffline;
This completes the example application. To test the offline capability, run the application, log in as either employee or manager, and then select Work offline. Modify the value in the text box, and then close the application. Restart the application. Before you log in, right-click the ASP.NET Development Server icon in the notification area of the taskbar and then click Stop. Then, log in like normal. Even when the server is not running, you can still log in. Modify the text box value, exit, and restart to see the modified value.
Summary
In this walkthrough, you learned how to enable and use client application services in a Windows Forms application. After setting up a test server, you added code to your application to authenticate users and retrieve user roles and application settings from the server. You also learned how to enable offline mode so that your application uses a local data cache instead of the remote services when connectivity is not available.
Next Steps
In a real-world application, you will access data for many users from a remote server that may not always be available, or may go down without notice. To make your application robust, you must respond appropriately to situations where the service is not available. This walkthrough includes try/catch blocks to catch a WebException and display an error message when the service is not available. In production code, you might want to handle this case by switching to offline mode, exiting the application, or denying access to specific functionality.
To increase the security of your application, make sure to thoroughly test the application and server before deployment.
See Also
Tasks
How to: Configure Client Application Services
Walkthrough: Using ASP.NET Application Services
Concepts
Client Application Services Overview
Other Resources
ASP.NET Web Site Administration Tool
Creating and Configuring the Application Services Database for SQL Server