
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1272 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
■Note If you make use of the ASP.NET session state server or SQL Server to store your session data, you must make sure that any custom types placed in the HttpSessionState object have been marked with the
[Serializable] attribute.
Understanding the ASP.NET Profile API
At this point in the chapter, you have examined numerous techniques that allow you to remember user-level and application-level bits of data. However, these techniques suffer from one major limitation: they exist only as long as the user is in session and the web application is running! However, many websites require the ability to persist user information across sessions. For example, perhaps you need to provide the ability for users to build an account on your site. Maybe you need to persist instances of a ShoppingCart class across sessions (for an online shopping site). Or perhaps you wish to persist basic user preferences (themes, etc.).
While you most certainly could build a custom database (with several stored procedures) to hold such information, you would then need to build a custom code library to interact with these database objects. This is not necessarily a complex task, but the bottom line is that you are the individual in charge of building this sort of infrastructure.
To help simplify matters, ASP.NET ships with an out-of-the-box user profile management API and database system for this very purpose. In addition to providing the necessary infrastructure, the Profile API also allows you to define the data to be persisted directly within your Web.config file (for purposes of simplification); however, you are also able to persist any [Serializable] type. Before we get too far ahead of ourselves, let’s check out where the Profile API will be storing the specified data.
The ASPNETDB.mdf Database
Every ASP.NET website built with Visual Studio 2008 automatically provides an App_Data subdirectory. By default, the Profile API (as well as other services, such as the ASP.NET role membership API; not examined in this text) is configured to make use of a local SQL Server 2008 database named ASPNETDB.mdf, located within the App_Data folder. This default behavior is due to settings within the machine.config file for the current .NET installation on your machine. In fact, when your code base makes use of any ASP.NET service requiring the App_Data folder, the ASPNETDB.mdf data file will be automatically created on the fly if a copy does not currently exist.
If you would rather have the ASP.NET runtime communicate with an ASPNETDB.mdf file located on another networked machine, or you would rather install this database on an instance of SQL Server 7.0 (or higher), you will need to manually build ASPNETDB.mdf using the aspnet_regsql.exe command-line utility. Like any good command-line tool, aspnet_regsql.exe provides numerous options; however, if you run the tool with no arguments, as so:
aspnet_regsql
you will launch a GUI-based wizard to help walk you through the process of creating and installing ASPNETDB.mdf on your machine (and version of SQL Server) of choice.
Now, assuming your site is not making use of a local copy of the database under the App_Data folder, the final step is to update your Web.config file to point to the unique location of your ASPNETDB.mdf. Assume you have installed ASPNETDB.mdf on a machine named ProductionServer. The following (partial) Web.config file (for a website named ShoppingCart) could be used to instruct the Profile API where to find the necessary database items:

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1273 |
<configuration>
<connectionStrings>
<add name="SqlServices"
connectionString ="Data Source=ProductionServer;Integrated Security=SSPI;Initial Catalog=aspnetdb;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<profile defaultProvider ="SqlProvider"> <providers>
<clear/>
<add name="AspNetSqlProfileProvider" connectionStringName="LocalSqlServer" applicationName="ShoppingCart" type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </providers>
</profile>
</system.web>
</configuration>
Like most *.config files, this looks much worse than it is. Basically we are defining a <connectionString> element with the necessary data, followed by a named instance of the SqlProfileProvider (this is the default provider used regardless of physical location of the ASPNETDB.mdf). If you require further information regarding this configuration syntax, be sure to check out the .NET Framework 3.5 SDK documentation.
■Note For simplicity, I will assume that you will simply make use of the autogenerated ASPNETDB.mdf database located under your web application’s App_Data subdirectory.
Defining a User Profile Within Web.config
As mentioned, a user profile is defined within a Web.config file. The really nifty aspect of this approach is that you can interact with this profile in a strongly typed manner using the inherited Profile property. To illustrate this, create a new website named FunWithProfiles and open your Web.config file for editing.
Our goal is to make a profile that models the home address of the users who are in session as well as the total number of times they have posted to this site. Not surprisingly, profile data is defined within a <profile> element using a set of name/data type pairs. Consider the following profile, which is created within the scope of the <system.web> element:
<profile>
<properties>
<add name="StreetAddress" type="System.String" /> <add name="City" type="System.String" />
<add name="State" type="System.String" /> <add name="TotalPost" type="System.Int32" /> </properties>
</profile>

1274 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
Here, we have specified a name and CLR data type for each item in the profile (of course, we could add additional items for zip code, name, and so forth, but I am sure you get the idea). Strictly speaking, the type attribute is optional; however, the default is a System.String. As you would guess, there are many other attributes that can be specified in a profile entry to further qualify how this information should be persisted in ASPNETDB.mdf. Table 33-4 illustrates some of the core attributes.
Table 33-4. Select Attributes of Profile Data
Attribute |
Example Values |
Meaning in Life |
allowAnonymous |
True | False |
Restricts or allows anonymous access to this |
|
|
value. If it is set to false, anonymous users |
|
|
won’t have access to this profile value. |
defaultValue |
String |
The value to return if the property has not |
|
|
been explicitly set. |
name |
String |
A unique identifier for this property. |
provider |
String |
The provider used to manage this value. It |
|
|
overrides the defaultProvider setting in |
|
|
Web.config or machine.config. |
readOnly |
True | False |
Restricts or allows write access. |
serializeAs |
String | XML | Binary |
The format of a value when persisting in the |
|
|
data store. |
type |
Primitive | User-defined type |
A .NET primitive type or class. Class names |
|
|
must be fully qualified (e.g., MyApp.UserData. |
|
|
ColorPrefs). |
|
|
|
We will see some of these attributes in action as we modify the current profile. For now, let’s see how to access this data programmatically from within our pages.
Accessing Profile Data Programmatically
Recall that the whole purpose of the ASP.NET Profile API is to automate the process of writing data to (and reading data from) a dedicated database. To test this out for yourself, update the UI of your Default.aspx file with a set of TextBoxes (and descriptive Labels) to gather the street address, city, and state of the user. As well, add a Button type (named btnSubmit) and a final Label (named lblUserData) that will be used to display the persisted data, as shown in Figure 33-11.
Now, within the Click event handler of the button, make use of the inherited Profile property to persist each point of profile data based on what the user has entered in the related TextBox. As you can see from Figure 33-12, Visual Studio 2008 will expose each bit of profile data as a strongly typed property. In effect, the Web.config file has been used to define a custom structure!


1276 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
Once you have persisted each piece of data within ASPNETDB.mdf, read each piece of data out of the database and format it into a String that is displayed on the lblUserData Label type. Finally, handle the page’s Load event, and display the same information on the Label type. In this way, when users come to the page, they can see their current settings. Here is the complete code file:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
GetUserAddress();
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
//Database writes happening here!
Profile.City = txtCity.Text; Profile.StreetAddress = txtStreetAddress.Text; Profile.State = txtState.Text;
//Get settings from database.
GetUserAddress();
}
private void GetUserAddress()
{
// Database reads happening here!
lblUserData.Text = String.Format("You live here: {0}, {1}, {2}", Profile.StreetAddress, Profile.City, Profile.State);
}
}
Now if you run this page, you will notice a lengthy delay the first time Default.aspx is requested. The reason is that the ASPNETDB.mdf file is being created on the fly and placed within your App_Data file. You can verify this for yourself by refreshing Solution Explorer (see Figure 33-13).
Figure 33-13. Behold ASPNETDB.mdf!
You will also find that the first time you come to this page, the lblUserData Label does not display any profile data, as you have not yet entered your data into the correct table of ASPNETDB.mdf. Once you enter values in the TextBox controls and post back to the server, you will find this Label is formatted with the persisted data, as shown in Figure 33-14.

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1277 |
Figure 33-14. Our persisted user data
Now, for the really interesting aspect of this technology. If you shut down your browser and rerun your website, you will find that your previously entered profile data has indeed been persisted, as the Label displays the correct information. This begs the obvious question, how were you remembered?
For this example, the Profile API made use of your Windows network identity, which was obtained by your current login credentials. However, when you are building public websites (where the users are not part of a given domain), rest assured that the Profile API integrates with the Formsbased authentication model of ASP.NET and also supports the notion of “anonymous profiles,” which allow you to persist profile data for users who do not currently have an active identity on your site.
■Note This edition of the text does not address ASP.NET security topics (such as Forms-based authentication or anonymous profiles). Consult the .NET Framework 3.5 SDK documentation for details.
Grouping Profile Data and Persisting Custom Objects
To wrap up this chapter, allow me to make a few additional comments on how profile data may be defined within a Web.config file. The current profile simply defined four pieces of data that were exposed directly from the profile type. When you build more complex profiles, it can be helpful to group related pieces of data under a unique name. Consider the following update:
<profile>
<properties>
<group name ="Address">
<add name="StreetAddress" type="String" /> <add name="City" type="String" />
<add name="State" type="String" />

1278 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
</group>
<add name="TotalPost" type="Integer" /> </properties>
</profile>
This time we have defined a custom group named Address to expose the street address, city, and state of our user. To access this data in our pages would now require us to update our code base by specifying Profile.Address to get each subitem. For example, here is the updated GetUserAddress() method (the Click event handler for the Button type would need to be updated in a similar manner):
private void GetUserAddress()
{
// Database reads happening here!
lblUserData.Text = String.Format("You live here: {0}, {1}, {2}", Profile.Address.StreetAddress,
Profile.Address.City, Profile.Address.State);
}
■Note A profile can contain as many groups as you feel are necessary. Simply define multiple <group> elements within your <properties> scope.
Finally, it is worth pointing out that a profile may also persist (and obtain) custom objects to and from ASPNETDB.mdf. To illustrate, assume that you wanted to build a custom class (or structure) that will represent the user’s address data. The only requirement expected by the Profile API is that the type be marked with the [Serializable] attribute, for example:
[Serializable]
public class UserAddress
{
public string Street = string.Empty; public string City = string.Empty; public string State = string.Empty;
}
With this class in place, our profile definition can now be updated as follows (notice I removed the custom group, although this is not mandatory):
<profile>
<properties>
<add name="AddressInfo" type="UserAddress" serializeAs ="Binary"/> <add name="TotalPost" type="Integer" />
</properties>
</profile>
Notice that when you are adding [Serializable] types to a profile, the type attribute is the fully qualified named of the type being persisted. Thus, if you were adding an ArrayList to a profile, type would be set to System.Collections.ArrayList. As well, you can control how this state data should be persisted into ASPNETDB.mdf using the serializeAs attribute. As you will see from the Visual Studio 2008 IntelliSense, your core choices are binary, XML, or string data.
Now that we are capturing street address information as a custom class type, we (once again) need to update our code base:

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1279 |
private void GetUserAddress()
{
// Database reads happening here!
lblUserData.Text = String.Format("You live here: {0}, {1}, {2}", Profile.AddressInfo.Street, Profile.AddressInfo.City, Profile.AddressInfo.State);
}
To be sure, there is much more to the Profile API than I have had space to cover here. For example, the Profile property actually encapsulates a type named ProfileCommon. Using this type, you are able to programmatically obtain all information for a given user, delete (or add) profiles to ASPNETDB.mdf, update aspects of a profile, and so forth.
As well, the Profile API has numerous points of extensibility that can allow you to optimize how the profile manager accesses the tables of the ASPNETDB.mdf database. As you would expect, there are numerous ways to decrease the number of “hits” this database takes. Interested readers are encouraged to consult the .NET Framework 3.5 SDK documentation for further details.
■Source Code The FunWithProfiles project is included under the Chapter 33 subdirectory.
Summary
In this chapter, you rounded out your knowledge of ASP.NET by examining how to leverage the HttpApplication type. As you have seen, this type provides a number of default event handlers that allow you to intercept various applicationand session-level events. The bulk of this chapter was spent examining a number of state management techniques. Recall that view state is used to automatically repopulate the values of HTML widgets between postbacks to a specific page. Next, you checked out the distinction of applicationand session-level data, cookie management, and the ASP.NET application cache.
The remainder of this chapter exposed you to the ASP.NET Profile API. As you have seen, this technology provides an out-of-the-box solution to the issue of persisting user data across sessions. Using your website’s Web.config file, you are able to define any number of profile items (including groups of items and [Serializable] types) that will automatically be persisted into ASPNETDB.mdf.


P A R T 8
Appendixes