client application services. You do this by adding the <profileService> element to the <system
.web.extensions> element in the web.config file:
734 .
chaPter 33 clienT ApplicATion SerViceS
<system.web.extensions>
<scripting>
<webServices>
<profileService enabled="true"
readAccessProperties="Nickname"
writeAccessProperties="Nickname" />
<authenticationService enabled="true" requireSSL="false"/>
Code snippet web.config
Following the previous examples, you will build a custom profile provider that uses an in-memory
dictionary to store user nicknames. Note that this isn’t a good way to track profile information,
because it would be lost every time the web server recycled and would not scale out to multiple
web servers. Nevertheless, you need to add a new class, CustomProfile, to the ApplicationServices
project and set it to inherit from ProfileProvider.
c#
using System.Web.Profile;
using System.Configuration;
public class CustomProfile : ProfileProvider{
private Dictionary<string, string> nicknames =
new Dictionary<string, string>();
public override System.Configuration.SettingsPropertyValueCollection
GetPropertyValues(System.Configuration.SettingsContext context,
System.Configuration.SettingsPropertyCollection collection){
var vals = new SettingsPropertyValueCollection();
foreach (SettingsProperty setting in collection){
var value = new SettingsPropertyValue(setting);
if (nicknames.ContainsKey(setting.Name)) {
value.PropertyValue = nicknames[setting.Name];
}
vals.Add(value);
}
return vals;
}
public override void SetPropertyValues(SettingsContext context,
SettingsPropertyValueCollection collection){
foreach (SettingsPropertyValue setting in collection){
nicknames[setting.Name] = setting.PropertyValue.ToString();
}
}
// The rest of the implementation has been omitted for brevity
}
Code snippet CustomProfile.cs
settings .
735
Vb
Imports System.Configuration
Public Class CustomProfile
Inherits ProfileProvider
Private nicknames As New Dictionary(Of String, String)
Public Overrides Function GetPropertyValues(ByVal context As SettingsContext,
ByVal collection As SettingsPropertyCollection) _
As SettingsPropertyValueCollection
Dim vals As New SettingsPropertyValueCollection
For Each setting As SettingsProperty In collection
Dim value As New SettingsPropertyValue(setting)
If nicknames.ContainsKey(setting.Name) Then
value.PropertyValue = nicknames.Item(setting.Name)
End If
vals.Add(value)
Next
Return vals
End Function
Public Overrides Sub SetPropertyValues(ByVal context As SettingsContext,
ByVal collection As SettingsPropertyValueCollection)
For Each setting As SettingsPropertyValue In collection
nicknames.Item(setting.Name) = setting.PropertyValue.ToString
Next
End Sub
'The rest of the implementation has been omitted for brevity
End Class
Code snippet CustomProfile.vb
The difference with the profile service is that when you specify the provider to use in the <system
.web> element in the web.config file, you also need to declare what properties can be saved via
the profile service (see the following snippet). For these properties to be accessible via the client
application services, they must have a corresponding entry in the readAccessProperties and
writeAccessProperties attributes of the <profileService> element, shown earlier.
<profile enabled="true" defaultProvider="CustomProfile">
<providers>
<add name="CustomProfile" type="ApplicationServices.CustomProfile"/>
</providers>
<properties>
<add name="Nickname" type="string"
readOnly="false" defaultValue="{nickname}"
serializeAs="String" allowAnonymous="false" />
</properties>
</profile>
Code snippet web.config
736 .
chaPter 33 clienT ApplicATion SerViceS
As an aside, the easiest way to build a full profile service is to use the utility aspnet_regsql.exe
(typically found at c:\Windows\Microsoft.NET\Framework\v4.0.21006\aspnet_regsql.exe) to
populate an existing SQL Server database with the appropriate table structure. You can then use the
built-in SqlProfileProvider (SqlMembershipProvider and SqlRoleProvider for membership
and role providers, respectively) to store and retrieve profile information. To use this provider,
change the profile element you added earlier to the following:
<profile enabled="true" defaultProvider="CustomProfile">
<providers>
<add name="SqlProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="SqlServices"
applicationName="SampleApplication"
description="SqlProfileProvider for SampleApplication" />
Note that the connectionStringName attribute needs to correspond to the name of a SQL Server
connection string located in the connectionStrings section of the web.config file.
To use the custom profile provider you have created, in the client application, you need to
specify the web settings service location on the Services tab of the project properties designer.
This location should be the same as for both the role and authentication services: http://
localhost:12345/ApplicationServices.
This is where the Visual Studio 2010 support for application settings is particularly useful. If you
now go to the Settings tab of the project properties designer and click the Load Web Settings button,
you are initially prompted for credential information, because you need to be a validated user to
access the profile service. Figure 33-3 shows this dialog with the appropriate credentials supplied.
After a valid set of credentials is entered, the profile service is interrogated and a new row is added to
the settings design surface, as shown in Figure 33-4. Here you can see that the scope of this setting is
indeed User (Web) and that the default value, specified in the web.config file, has been retrieved.
fiGure 33-3 fiGure 33-4
If you take a look at the app.config file for the client application, you will notice that a new
sectionGroup has been added to the configSections element. This simply declares the class that
will be used to process the custom section that has been added to support the new user settings.
settings .
737
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup, System,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section name="ClientServices.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" allowExeDefinition=
"MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
Toward the end of the app.config file, you will see the custom section that has been created. As you
would expect, the name of the setting is Nickname and the value corresponds to the default value
specified in the web.config file in the ApplicationServices project.
<userSettings>
<ClientAppServicesVB.MySettings>
<setting name="Nickname" serializeAs="String">
<value>{nickname}</value>
</setting>
</ClientAppServicesVB.MySettings>
</userSettings>
To make use of this in code you can use the same syntax as for any other setting. Here you simply
retrieve the current value, request a new value, and then save this new value:
c#
private void Window_Loaded(object sender, RoutedEventArgs e){
// Commented out for brevity
MessageBox.Show(My.Settings.Nickname)
Properties.Settings.Default.Nickname = "Not the default Name";
My.Settings.Save()
Code snippet MainWindow.cs
Vb
Private Sub Window_Loaded(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs) _
Handles Me.Loaded
' Commented out for brevity
MessageBox.Show(My.Settings.Nickname)
My.Settings.Nickname = InputBox("Please specify a nickname:", "Nickname")
My.Settings.Save()
Code snippet MainWindow.vb
If you run this application again, the nickname you supplied the first time will be returned.
738 .
chaPter 33 clienT ApplicATion SerViceS
loGin forM
Earlier, when you were introduced to Forms authentication, you used a hard-coded username and
password to validate the user. Although it would be possible for the application to prompt the user
for credentials before calling ValidateUser with the supplied values, there is a better way that
uses the client application services framework. Instead of calling ValidateUser with a username/
password combination, you go back to supplying Nothing as the argument values and define a
credential provider; then the client application services will call the provider to determine the set of
credentials to use.
c#
private void Window_Loaded(object sender, RoutedEventArgs e){
if (Membership.ValidateUser(null, null)){
MessageBox.Show("User is valid");
Code snippet MainWindow.cs
Vb
Private Sub Window_Loaded(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs) _
Handles Me.Loaded
If Membership.ValidateUser(Nothing, Nothing) Then
MessageBox.Show("User is valid")
Code snippet MainWindow.vb
This probably sounds more complex than it is because it is relatively easy to create a credentials
provider. Start by adding a login form to the client application. Do this by selecting the Login Form
template from the Add New Item dialog and calling it LoginForm. Unfortunately, this template is
only available for VB developers as a Windows Forms form. If you want to create a WPF version
or are working in C# you will need to add a new Window to the ClientServices project and add a
TextBox (name it UsernameTextBox), a PasswordBox (name it PasswordTextBox), and two Buttons
(name them OK and Cancel). While you have the designer open, click the OK button and change the
DialogResult property to OK.
To use this login form as a credential provider, modify it to implement the
IClientFormsAuthenticationCredentialsProvider interface. An alternative strategy
would be to have a separate class that implements this interface and then displays the
login form when the GetCredentials method is called. The following code snippet
contains the code-behind file for the LoginForm class, showing the implementation of the
IClientFormsAuthenticationCredentialsProvider interface:
c#
using System.Web.ClientServices.Providers;
public partial class LoginForm : Window,
IClientFormsAuthenticationCredentialsProvider {
public LoginForm(){
login form .
739
InitializeComponent();
}
private void OK_Click(object sender, RoutedEventArgs e){
this.DialogResult = true;
this.Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e){
this.DialogResult = false;
this.Close();
}
public ClientFormsAuthenticationCredentials GetCredentials(){
if (this.ShowDialog() false) {
return new ClientFormsAuthenticationCredentials(
UsernameTextBox.Text,
PasswordTextBox.
Password,
false);
}
else{
return null;
}
}
}
Code Snippet LoginForm.xaml.cs
Vb
Imports System.Web.ClientServices.Providers
Public Class LoginForm
Implements IClientFormsAuthenticationCredentialsProvider
Public Function GetCredentials() As ClientFormsAuthenticationCredentials _
Implements IClientFormsAuthenticationCredentialsProvider.GetCredentials
If Me.ShowDialog() = Forms.DialogResult.OK Then
Return New ClientFormsAuthenticationCredentials(UsernameTextBox.Text,
PasswordTextBox.Text,
False)
Else
Return Nothing
End If
End Function
End Class
Code snippet LoginForm.vb
You will notice that the C# and VB code snippets are quite different. This
is because the C# uses a new WPF window, while the VB snippet uses the
Windows Form Login Form template.
740 .
chaPter 33 clienT ApplicATion SerViceS
As you can see from this snippet, the GetCredentials method returns
ClientFormsAuthenticationCredentials if credentials are supplied, or Nothing (VB)/null
(C#) if Cancel is clicked. Clearly this is only one way to collect credentials information, and there
is no requirement that you prompt the user for this information. (The use of dongles or employee
identification cards are common alternatives.)
With the credentials provider created, it is just a matter of informing the client application services
that they should use it. You do this via the Optional: Credentials Provider field on the Services tab
of the project properties designer, as shown in Figure 33-5.
Now when you run the application, you are prompted to enter a username and password to access
the application. This information is then passed to the membership provider on the server to
validate the user.
fiGure 33-5
offline suPPort
In the previous steps, if you had a breakpoint
in the role provider code on the server, you
may have noticed that it hit the breakpoint
only the first time you ran the application.
The reason for this is that it is caching the role
information offline. If you click the Advanced
button on the Services tab of the project
properties designer, you will see a number of
properties that can be adjusted to control this
fiGure 33-6
offline behavior, as shown in Figure 33-6.
offline support .
741
It’s the role service cache timeout that determines how frequently the server is queried for role