4.12. Store Personalized Information
ASP.NET applications often have the need to store user-specific information beyond the bare minimum username and password. One way to solve this problem is to use the Session collection. Session state has two limitations: it isn't permanent (typically, a session times out after 20 minutes of inactivity), and it isn't strongly typed (in other words, you need to know what's in the session collection and manually cast references to the appropriate data types). ASP.NET 2.0 addresses these limitations with a new framework for storing user-specific data called profile settings.
Note: Need to store some custom user-specific information for long periods of time? Why not use the membership data provider to save and retrieve information without resorting to database code.
4.12.1. How do I do that?
Profiles build on the same provider model that's used for membership and role management. Essentially, the profile provider takes care of storing all the user-specific information in some backend data store. Currently, ASP.NET includes a profile provider that's tailored for SQL Server.
Before you start using profiles, you should have a system in place for authenticating users. That's because personalized information needs to be linked to a specific user, so that you can retrieve it on subsequent visits. Typically, you'll use forms authentication, with the help of the ASP.NET membership services described in the lab "Easily Authenticate Users."
With profiles, you need to define the type of user-specific information you want to store. In early builds, the WAT included a tool for generating profile settings. However, this tool has disappeared in later releases, and unless (or until) it returns, you need to define your profile settings in the web.config file by hand. Here's an example of a profile section that defines a single string named Fullname:
Note: ASP. NET does include basic features that allow you to use personalization with anonymous users (see the "What about..." section of this lab for more information).
<?xml version="1.0"?> <configuration> <system.web> <profile> <properties> <add name="FullName" type="System.String" /> </properties> </profile> <!-- Other settings ommitted. --> </system.web> </configuration>
Initially, this doesn't seem any more useful than an application setting. However, Visual Studio automatically generates a new class based on your profile settings. You can access this class through the Page.Profile property. The other benefit is the fact that ASP.NET stores this information in a backend database, automatically retrieving it from the database at the beginning of the request and writing it back at the end of the request (if these operations are needed). In other words, profiles give you a higher-level model for maintaining user-specific information that's stored in a database.
In other words, assuming you've defined the FullName property in the <profile> section, you can set and retrieve a user's name information using code like this:
Profile.FullName = "Joe Smythe" ... lblName.Text = "Hello " & Profile.FullName
Note that the Profile class is strongly typed. There's no need to convert the reference, and Visual Studio's IntelliSense springs into action when you type Profile followed by the period.
Life gets even more interesting if you want to store a full-fledged object. For example, imagine you create specialized classes to track the products in a user's shopping basket. Example 4-6 shows a Basket class that contains a collection of BasketItem objects, each representing a separate product.
Example 4-6. Custom classes for a shopping cart
Imports System.Collections.Generic Public Class Basket Private _Items As New List(Of BasketItem) Public Property Items( ) As List(Of BasketItem) Get Return _Items End Get Set(ByVal value As List(Of BasketItem)) _Items = value End Set End Property End Class Public Class BasketItem Private _Name As String Public Property Name( ) As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _ID As String = Guid.NewGuid( ).ToString( ) Public Property ID( ) As String Get Return _ID End Get Set(ByVal value As String) _ID = value End Set End Property Public Sub New(ByVal name As String) _Name = name End Sub Public Sub New( ) ' Used for serialization. End Sub End Class
To use this class, you need to add it to the Code subdirectory so that it's compiled automatically. Then, to make it a part of the user profile, you need to define it in the web.config file, like this:
<profile> <properties> <add name="Basket" type="Basket" /> </properties> </profile>
With this information in place, it's easy to create a simple shopping cart test page. Figure 4-16 shows an example that lets you add and remove items. When the page is first loaded, it checks if there is a shopping basket for the current user, and if there isn't, it creates one. The user can then add items to the cart or remove existing items, using the Add and Remove buttons. Finally, the collection of shopping basket items is bound to a listbox every time the page is rendered, ensuring the page shows the current list of items in the basket. Example 4-7 shows the complete code.
Figure 4-16. Adding items to a shopping basket
Example 4-7. Testing a personalized shopping basket
<%@ Page language="VB" %> <script runat="server"> Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) If Profile.Basket Is Nothing Then Profile.Basket = New Basket( ) End Sub ' Put a new item in the basket. Sub cmdAdd_Click(ByVal sender As Object, ByVal e As System.EventArgs) Profile.Basket.Items.Add(New BasketItem(txtItemName.Text)) End Sub ' Remove the selected item. Sub cmdRemove_Click(ByVal sender As Object, ByVal e As System.EventArgs) For Each Item As BasketItem In Profile.Basket.Items If Item.ID = lstItems.SelectedItem.Value Then Profile.Basket.Items.Remove(Item) Return End If Next End Sub ' The page is being rendered. Create the list using data binding. Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) lstItems.DataSource = Profile.Basket.Items lstItems.DataTextField = "Name" lstItems.DataValueField = "ID" lstItems.DataBind( ) End Sub </script> <html> <head runat="server"> <title>Test Page</title> </head> <body> <form runat="server"> <br /> <br /> <asp:ListBox Runat="server" Width="266px" Height="106px"></asp:ListBox><br /> <asp:TextBox Runat="server" Width="266px"></asp:TextBox><br /> <asp:Button Runat="server" Width="106px" Text="Add New Item" OnClick="cmdAdd_Click" /> <asp:Button Runat="server" Width="157px" Text="Remove Selected Item" OnClick="cmdRemove_Click" /> </form> </body> </html>
Remember, profile information doesn't time out. That means that even if you rebuild and restart the web application, the shopping cart items will still be there, unless your code explicitly clears them. This makes profiles perfect for storing permanent user-specific information without worrying about the hassle of ADO.NET code.
4.12.2. What about...
...anonymous users? By default, you can only access profile information once a user has logged in. However, many web sites retain user-specific information even when users aren't logged in. For example, most online e-commerce shops let users start shopping immediately, and only force them to log in at checkout time. To implement this design (without resorting to session state), you need to use another new feature in ASP.NETanonymous identification.
With anonymous identification, ASP.NET assigns a unique ID to every new user. This ID is stored in a persistent cookie, which means that even if a user waits several days before making a repeat visit, ASP.NET will still be able to identify the user and find the personalized information from the user's last visit. (The default expiration settings remove the cookie after about one week if the user hasn't returned.)
In order to use anonymous identification, you need to add the <anonymousIdentification> tag to the web.config file, and you need to explicitly indicate what profile information can be tracked anonymously by flagging these properties with the allowAnonymous attribute.
Here's an example with a revised web.config that stores shopping basket information for anonymous users:
<?xml version="1.0"?> <configuration> <system.web> <anonymousIdentification enabled="true"> <profile> <properties> <add name="Basket" type="Basket" allowAnonymous="true"/> </properties> </profile> <!-- Other settings ommitted. --> </system.web> </configuration>
Anonymous identification raises a few new considerations. The most significant occurs in systems where an anonymous user needs to log in at some point to complete an operation. In order to make sure information isn't lost, you need to handle the PersonalizationModule.MigrateAnonymous event in the global.asax file. You can then transfer information from the anonymous profile to the new authenticated profile.
4.12.3. Where can I learn more?
For more information about various profile options, including transferring anonymous profile information into an authenticated profile, look for the index entry "profiles" in the MSDN Help.