The ASP.NET 2.0 Framework provides you with an alternative to using cookies or Session state to store user information: the Profile object. The Profile object provides you with a strongly typed, persistent form of session state. You create a Profile by defining a list of Profile properties in your application root web configuration file. The ASP.NET Framework dynamically compiles a class that contains these properties in the background. For example, the web configuration file in Listing 22.19 defines a Profile that contains three properties: firstName, lastName, and numberOfVisits. Listing 22.19. Web.Config
When you define a Profile property, you can use any of the following attributes:
After you define a Profile in the web configuration file, you can use the Profile object to modify the Profile properties. For example, the page in Listing 22.20 enables you to modify the firstName and lastName properties with a form. Furthermore, the page automatically updates the numberOfVisits property each time the page is requested (see Figure 22.7). Figure 22.7. Displaying Profile information.Listing 22.20. ShowProfile.aspx
Notice that Profile properties are exposed as strongly typed properties. The numberOfVisits property, for example, is exposed as an integer property because you defined it as an integer property. It is important to understand that Profile properties are persistent. If you set a Profile property for a user, and that user does not return to your web site for 500 years, the property retains its value. Unlike Session state, when you assign a value to a Profile property, the value does not evaporate after a user leaves your website. The Profile object uses the Provider model. The default Profile provider is the SqlProfileProvider. By default, this provider stores the Profile data in a Microsoft SQL Server 2005 Express database named ASPNETDB.mdf, located in your application's App_Code folder. If the database does not exist, it is created automatically the first time that you use the Profile object. By default, you cannot store Profile information for an anonymous user. The ASP.NET Framework uses your authenticated identity to associate Profile information with you. You can use the Profile object with any of the standard types of authentication supported by the ASP.NET Framework, including both Forms and Windows authentication. (Windows authentication is enabled by default.) Note Later in this section, you learn how to store Profile information for anonymous users. Creating Profile GroupsIf you need to define a lot of Profile properties, then you can make the properties more manageable by organizing the properties into groups. For example, the web configuration file in Listing 22.21 defines two groups named Preferences and ContactInfo. Listing 22.21. Web.Config
The page in Listing 22.22 illustrates how you can set and read properties in different groups. Listing 22.22. ShowProfileGroups.aspx
Supporting Anonymous UsersBy default, anonymous users cannot modify Profile properties. The problem is that the ASP.NET Framework has no method of associating Profile data with a particular user unless the user is authenticated. If you want to enable anonymous users to modify Profile properties, you must enable a feature of the ASP.NET Framework called Anonymous Identification. When Anonymous Identification is enabled, a unique identifier (a GUID) is assigned to anonymous users and stored in a persistent browser cookie. Note You can enable cookieless anonymous identifiers. Cookieless anonymous identifiers work just like cookieless sessions: The anonymous identifier is added to the page URL instead of a cookie. You enable cookieless anonymous identifiers by setting the cookieless attribute of the anonymousIdentification element in the web configuration file to the value UseURI or AutoDetect. Furthermore, you must mark all Profile properties that you want anonymous users to be able to modify with the allowAnonymous attribute. For example, the web configuration file in Listing 22.23 enables Anonymous Identification and defines a Profile property that can be modified by anonymous users. Listing 22.23. Web.Config
The numberOfVisits property defined in Listing 22.23 includes the allowAnonymous attribute. Notice that the web configuration file also enables Forms authentication. When Forms authentication is enabled, and you don't log in, then you are an anonymous user. The page in Listing 22.24 illustrates how you modify a Profile property when Anonymous Identification is enabled. Listing 22.24. ShowAnonymousIdentification.aspx
Each time that you request the page in Listing 22.24, the numberOfVisits Profile property is incremented and displayed. The page includes three buttons: Reload, Login, and Logout (see Figure 22.8). Figure 22.8. Creating an anonymous profile.The page also displays the value of the Profile.UserName property. This property represents either the current username or the anonymous identifier. The value of the numberOfVisits Profile property is tied to the value of the Profile.UserName property. You can click the Reload button to quickly reload the page and increment the value of the numberOfVisits property. If you click the Login button, then the Profile.UserName property changes to the value Bob. The numberOfVisits property is reset. If you click the Logout button, then the Profile.UserName property switches back to your anonymous identifier. The numberOfVisits property reverts to its previous value. Migrating Anonymous ProfilesIn the previous section, you saw that all profile information is lost when a user transitions from anonymous to authenticated. For example, if you store a shopping cart in the Profile object and a user logs in, then all the shopping cart items are lost. You can preserve the value of Profile properties when a user transitions from anonymous to authenticated by handling the MigrateAnonymous event in the Global.asax file. This event is raised when an anonymous user that has a profile logs in. For example, the MigrateAnonymous event handler in Listing 22.25 automatically copies the values of all anonymous Profile properties to the user's current authenticated profile. Listing 22.25. Global.asax
The anonymous Profile associated with the user is retrieved when the user's anonymous identifier is passed to the Profile.GetProfile() method. Next, each Profile property is copied from the anonymous Profile to the current Profile. Finally, the anonymous Profile is deleted and the anonymous identifier is destroyed. (If you don't destroy the anonymous identifier, then the MigrateAnonymous event continues to be raised with each page request after the user authenticates.) Inheriting a Profile from a Custom ClassInstead of defining a list of Profile properties in the web configuration file, you can define Profile properties in a separate class. For example, the class in Listing 22.26 contains two properties named FirstName and LastName. Listing 22.26. App_Code\SiteProfile.vb
Notice that the class in Listing 22.26 inherits from the BaseProfile class. After you declare a class, you can use it to define a profile by inheriting the Profile object from the class in the web configuration file. The web configuration file in Listing 22.27 uses the inherits attribute to inherit the Profile from the SiteProfile class. Listing 22.27. Web.Config
After you inherit a Profile in the web configuration file, you can use the Profile in the normal way. You can set or read any of the properties that you defined in the SiteProfile class by accessing the properties through the Profile object. Note The CD that accompanies this book includes a page named ShowSiteProfile.aspx, which displays the Profile properties defined in Listing 22.27. Note If you inherit Profile properties from a class and define Profile properties in the web configuration file, then the two sets of Profile properties are merged. When you define Profile properties in a class, you can decorate the properties with the following attributes:
For example, both properties declared in the SiteProfile class in Listing 22.28 include the SettingsAllowAnonymous attribute, which allows anonymous users to read and modify the properties. Creating Complex Profile PropertiesTo this point, we've used the Profile properties to represent simple types such as strings and integers. You can use Profile properties to represent more complex types such as a custom ShoppingCart class. For example, the class in Listing 22.28 represents a simple shopping cart. Listing 22.28. App_Code\ShoppingCart.vb
The file in Listing 22.28 actually contains two classes: the ShoppingCart class and the CartItem class. The ShoppingCart class exposes a collection of CartItem objects. The web configuration file in Listing 22.29 defines a Profile property named ShoppingCart that represents the ShoppingCart class. The type attribute is set to the fully qualified name of the ShoppingCart class. Listing 22.29. Web.Config
Finally, the page in Listing 22.30 uses the Profile.ShoppingCart property. The contents of the ShoppingCart are bound and displayed in a GridView control. The page also contains a form that enables you to add new items to the ShoppingCart (see Figure 22.9). Figure 22.9. Storing a shopping cart in a profile.Listing 22.30. ShowShoppingCart.aspx
If you want to take control over how complex properties are stored, you can modify the value of the serializeAs attribute associated with a Profile property. The serializeAs attribute accepts the following four values:
The default value, when using the SqlProfileProvider, is ProviderSpecific. In other words, the SqlProfileProvider decides on the best method for storing properties. In general, simple types are serialized as strings and complex types are serialized with the XML Serializer. One disadvantage of the XML Serializer is that it produces a more bloated representation of a property than the Binary Serializer. For example, the results of serializing the ShoppingCart class with the XML Serializer are contained in Listing 22.31: Listing 22.31. Serialized Shopping Cart
If you want to serialize a Profile property with the Binary Serializer (and save some database space) then you need to do two things. First, you need to indicate in the web configuration file that the Profile property should be serialized with the Binary Serializer. Furthermore, you need to mark the class that the Profile property represents as serializable. The modified ShoppingClass (named BinaryShoppingCart) in Listing 22.32 includes a Serializable attribute. Notice that both the BinaryShoppingCart and BinaryCartItem classes are decorated with the Serializable attribute. Listing 22.32. App_Code\BinaryShoppingCart.vb
The Profile in the web configuration file in Listing 22.33 includes a property that represents the BinaryShoppingCart class. Notice that the property includes a serializeAs attribute that has the value Binary. If you don't include this attribute, the BinaryShoppingCart will be serialized as XML. Listing 22.33. Web.Config
Note The CD that accompanies this book includes a page named ShowBinaryShoppingCart.aspx that displays the BinaryShoppingCart. Saving Profiles AutomaticallyA profile is loaded from its profile provider the first time that a property from the profile is accessed. For example, if you use a Profile property in a Page_Load() handler, then the profile is loaded during the Page Load event. If you use a Profile property in a Page_PreRender() handler, then the Profile is loaded during the page PreRender event. If a Profile property is modified, then the Profile is saved automatically at the end of page execution. The ASP.NET Framework can detect automatically when certain types of properties are changed but not others. In general, the ASP.NET Framework can detect changes made to simple types but not to complex types. For example, if you access a property that exposes a simple type such as a string, integer, or Datetime, then the ASP.NET Framework can detect when the property has been changed. In that case, the framework sets the Profile.IsDirty property to the value true. At the end of page execution, if a profile is marked as dirty, then the profile is saved automatically. The ASP.NET Framework cannot detect when a Profile property that represents a complex type has been modified. For example, if your profile includes a property that represents a custom ShoppingCart class, then the ASP.NET Framework has no way of determining when the contents of the ShoppingCart class have been changed. The ASP.NET Framework errs on the side of caution. If you access a complex Profile property at alleven if you simply read the propertythe ASP.NET Framework sets the Profile.IsDirty property to the value true. In other words, if you read a complex property, the profile is always saved at the end of page execution. Because storing a profile at the end of each page execution can be an expensive operation, the ASP.NET Framework provides you with two methods of controlling when a profile is saved. First, you can take the responsibility of determining when a profile is saved. The web configuration file in Listing 22.34 disables the automatic saving of profiles by setting the autoSaveEnabled property to the value false. Listing 22.34. Web.Config
After you disable the automatic saving of profiles, you must explicitly call the Profile.Save() method to save a profile after you modify it. For example, the btnAdd_Click() method in Listing 22.35 explicitly calls the Profile.Save() method when a new item has been added to the shopping cart. Listing 22.35. ShowExplicitSave.aspx
As an alternative to disabling the automatic saving of profiles, you can write custom logic to control when a profile is saved by handling the ProfileAutoSaving event in the Global.asax file. For example, the Global.asax file in Listing 22.36 saves a profile only when the Profile.ShoppingCart.HasChanged property has been assigned the value true. Listing 22.36. Global.asax
Note The CD that accompanies this book includes the shopping cart class and ASP.NET page that accompany the Global.asax file in Listing 22.37. The class is named ShoppingCartHasChanged.vb and the page is named ShowShoppingCartHasChanged.aspx. You'll need to modify the web configuration file so that the profile inherits from the ShoppingCartHasChanged class. Accessing Profiles from ComponentsYou can access the Profile object from within a component by referring to the HttpContext.Profile property. However, you must cast the value of this property to an instance of the ProfileCommon object before you access its properties. For example, the web configuration file in Listing 22.37 defines a Profile property named firstName. Listing 22.37. Web.Config
The component in Listing 22.38 grabs the value of the firstName Profile property. Notice that the Profile object retrieved from the current HttpContext object must be case to a ProfileCommon object. Listing 22.38. App_Code\ProfileComponent.vb
Warning To avoid conflicts with other code samples in this chapter, the component in Listing 22.38 is named ProfileComponent.vb_listing38 on the CD that accompanies this book. You'll need to rename the file to ProfileComponent.vb before you use the component. Finally, the page in Listing 22.39 illustrates how you can call the ProfileComponent from within an ASP.NET page to retrieve and display the firstName attribute. Listing 22.39. ShowProfileComponent.aspx
Using the Profile ManagerUnlike Session state, profile data does not evaporate when a user leaves your application. Over time, as more users visit your application, the amount of data stored by the Profile object can become huge. If you allow anonymous profiles, the situation becomes even worse. The ASP.NET Framework includes a class named the ProfileManager class that enables you to delete old profiles. This class supports the following methods:
You can use the ProfileManager class from within a console application and execute the DeleteInactiveProfiles() method on a periodic basis to delete inactive profiles. Alternatively, you can create an administrative page in your web application that enables you to manage profile data. The page in Listing 22.40 illustrates how you can use the ProfileManager class to remove inactive profiles (see Figure 22.10). Figure 22.10. Deleting inactive profiles.Listing 22.40. ManageProfiles.aspx
The page in Listing 22.40 displays the total number of profiles and the total number of inactive profiles. An inactive profile is a profile that has not been accessed for more than three months. The page also includes a Delete Inactive Profiles button that enables you to remove the old profiles. Configuring the Profile ProviderBy default, profile data is stored in a Microsoft SQL Server Express database named ASPNETDB.mdf, located in your application's root App_Data folder. If you want to store profile data in another database in your network, then you need to perform the following two tasks:
You can add the necessary database tables and stored procedures required by the Profile object to a database by executing the aspnet_regsql command-line tool. The aspnet_regsql tool is located at the following path: \WINDOWS\Microsoft.NET\Framework\[version]\aspnet_regsql.exe Note If you open the SDK Command Prompt, then you do not need to navigate to the Microsoft.NET directory to execute the aspnet_regsql tool. If you execute this tool without supplying any parameters, then the ASP.NET SQL Server Setup Wizard launches. This wizard guides you through the process of connecting to a database and adding the necessary database objects. As an alternative to using the aspnet_regsql tool, you can install the necessary database objects by executing the following two SQL batch files: \WINDOWS\Microsoft.NET\Framework\[version]\InstallCommon.sql \WINDOWS\Microsoft.NET\Framework\[version]\InstallProfile.sql After you have set up your database, you need to configure the default profile provider to connect to the database. The web configuration file in Listing 22.41 connects to a database named MyDatabase on a server named MyServer. Listing 22.41. Web.Config
After you complete these configuration steps, all profile data is stored in a custom database. Creating a Custom Profile ProviderThe Profile object uses the Provider Model. The ASP.NET Framework includes a single profile provider, the SqlProfileProvider, that stores profile data in a Microsoft SQL Server database. In this section, you learn how to build a custom profile provider. One problem with the default SqlProfileProvider is that it serializes an entire profile into a single blob and stores the blob in a database table column. This means that you can't execute SQL queries against the properties in a profile. In other words, the default SqlProfileProvider makes it extremely difficult to generate reports off the properties stored in a profile. In this section, we create a new profile provider that is modestly named the BetterProfileProvider. The BetterProfileProvider stores each Profile property in a separate database column. Unfortunately, the code for the BetterProfileProvider is too long to place in this book. However, the entire source code is included on the CD that accompanies this book. The BetterProfileProvider inherits from the base ProfileProvider class. The two most important methods that must be overridden in the base ProfileProvider class are the GetPropertyValues() and SetPropertyValues() methods. These methods are responsible for loading and saving a profile for a particular user. Imagine that you want to use the BetterProfileProvider to represent a profile that contains the following three properties: FirstName, LastName, and NumberOfVisits. Before you can use the BetterProfileProvider, you must create a database table that contains three columns that correspond to these Profile properties. In addition, the database table must contain an int column named ProfileID. You can create the necessary database table with the following SQL command: CREATE TABLE ProfileData { ProfileID Int, FirstName NVarChar(50), LastName NVarChar(50), NumberOfVisits Int } Next, you need to create a database table named Profiles. This table is used to describe the properties of each profile. You can create the Profiles table with the following SQL command: CREATE TABLE Profiles ( UniqueID IDENTITY NOT NULL PRIMARY KEY, UserName NVarchar(255) NOT NULL, ApplicationName NVarchar(255) NOT NULL, IsAnonymous BIT, LastActivityDate DateTime, LastUpdatedDate DateTime, ) After you create these two database tables, you are ready to use the BetterProfileProvider. The web configuration file in Listing 22.42 configures the BetterProfileProvider as the default profile provider. Listing 22.42. Web.Config
Notice that the BetterProfileProvider is configured with both a connectionStringName and profileTableName attribute. The connectionStringName points to the database that contains the two database tables that were created earlier. The profileTableName property contains the name of the table that contains the profile data. (This attribute defaults to the value ProfileData, so it really isn't necessary here.) After you configure the BetterProfileProvider, you can use it in a similar manner to the default SqlProfileProvider. For example, the page in Listing 22.43 displays the values of the FirstName, LastName, and NumberOfVisits profile properties and enables you to modify the FirstName and LastName properties. Warning The BetterProfileProvider has several important limitations. It does not support serialization, so you cannot use it with complex types such as a custom shopping cart class. It also does not support default values for Profile properties. Listing 22.43. ShowBetterProfileProvider.aspx
The main advantage of the BetterProfileProvider is that you can perform SQL queries against the data stored in the ProfileData table. For example, the page in Listing 22.44 displays the contents of the ProfileData table in a GridView control (see Figure 22.11). You can't do this when using the default SqlProfileProvider because the SqlProfileProvider stores profile data in a blob. Figure 22.11. Displaying a profile report.Listing 22.44. BetterProfileProviderReport.aspx
|