Page Personalization

 

Page Personalization

ASP.NET pages do not necessarily require a rich set of personalization features. However, if you can build an effective personalization layer into your Web application, final pages will be friendlier, more functional, and more appealing to use. For some applications (such as portals and shopping centers), though, personalization is crucial. For others, it is mostly a way to improve visual appearance. In ASP.NET 2.0, personalization comes in two complementary forms: user profiles and themes.

The user profile is designed for persistent storage of structured data using a friendly and type-safe API. The application defines its own model of personalized data, and the ASP.NET runtime does the rest by parsing and compiling that model into a class. Each member of the personalized class data corresponds to a piece of information specific to the current user. Loading and saving personalized data is completely transparent to end users and doesn't even require the page author to know much about the internal plumbing.

Themes assign a set of styles and visual attributes to elements of the site that can be customized. These elements include control properties, page style sheets, images, and templates on the page. A theme is the union of all visual styles for all customizable elements in the pages a sort of super-CSS file. We'll work on themes in the next chapter.

Important 

No personalization facilities are available in ASP.NET 1.x. I discussed how to build a similar infrastructure in the March 2004 issue of MSDN Magazine. The article is online at http://msdn.microsoft.com/msdnmag/issues/04/03/CuttingEdge.

Creating the User Profile

At the highest level of abstraction, a user profile is a collection of properties that the ASP.NET 2.0 runtime groups into a dynamically generated class. Any profile data is persisted on a per-user basis and is permanently stored until someone with administrative privileges deletes it. When the application runs and a page is displayed, ASP.NET dynamically creates a profile object that contains, properly typed, the properties you have defined in the data model. The object is then added to the current HttpContext object and is available to pages through the Profile property.

The data storage is hidden from the user and, to some extent, from the programmers. The user doesn't need to know how and where the data is stored; the programmer simply needs to indicate what type of profile provider he wants to use. The profile provider determines the database to use typically, a Microsoft SQL Server database but custom providers and custom data storage models can also be used.

Note 

In ASP.NET 2.0, the default profile provider is based on SQL Express, a lightweight version of SQL Server 2005. The physical storage medium is a local file named aspnetdb.mdf, which is located in the App_Data folder of the Web application.

Definition of the Data Model

To use the ASP.NET 2.0 profile API, you first decide on the structure of the data model you want to use. Then you attach the data model to the page through the configuration file. The layout of the user profile is defined in the web.config file and consists of a list of properties that can take any of the .NET common language runtime (CLR) types. The data model is a block of XML data that describe properties and related .NET Framework types.

The simplest way to add properties to the profile storage medium is through name/value pairs. You define each pair by adding a new property tag to the <properties> section of the configuration file. The <properties> section is itself part of the larger <profile> section, which also includes provider information. The <profile> section is located under <system.web>. Here's an example of a user profile section:

<profile>     <properties>         <add name="BackColor" type="string" />         <add name="ForeColor" type="string" />     </properties> </profile> 

All the properties defined through an <add> tag become members of a dynamically created class that is exposed as part of the HTTP context of each page. The type attribute indicates the type of the property. If no type information is set, the type defaults to System.String. Any valid CLR type is acceptable. Table 5-3 lists the valid attributes for the <add> element. Only name is mandatory.

Table 5-3: Attributes of the <add> Element

Attribute

Description

allowAnonymous

Allows storing values for anonymous users. False by default.

defaultValue

Indicates the default value of the property.

customProviderData

Contains data for a custom profile provider.

Name

Name of the property.

Provider

Name of the provider to use to read and write the property.

readOnly

Specifies whether the property value is read-only. False by default.

serializeAs

Indicates how to serialize the value of the property. Possible values are Xml, Binary, String, and ProviderSpecific.

Type

The .NET Framework type of property. It is a string object by default.

The User Profile Class Representation

As a programmer, you don't need to know how data is stored or retrieved from the personalization store. However, you must create and configure the store. We skirted this step, but we'll discuss it in detail shortly. The following code snippet gives you an idea of the class being generated to represent the profile's data model:

namespace ASP {     public class ProfileCommon : ProfileBase     {         public virtual string BackColor         {             get {(string) GetPropertyValue("BackColor");}             set {SetPropertyValue("BackColor", value);}         }         public virtual string ForeColor         {             get {(string) GetPropertyValue("ForeColor");}             set {SetPropertyValue("ForeColor", value);}         }         public virtual ProfileCommon GetProfile(string username)         {             object o = ProfileBase.Create(username);             return (ProfileCommon) o;         }         ...     } } 

An instance of this class is associated with the Profile property of the page class and is accessed programmatically as follows:

// Use the BackColor property to paint the page background theBody.Attributes["bgcolor"] = Profile.BackColor; 

There's a tight relationship between user accounts and profile information. We'll investigate this in a moment for now, you need to notice this because anonymous users are supported as well.

Using Collection Types

In the previous example, we worked with single, scalar values. However, the personalization engine fully supports more advanced scenarios, such as using collections or custom types. Let's tackle collections first. The following code demonstrates a property Links that is a collection of strings:

<properties>     <add name="Links"         type="System.Collections.Specialized.StringCollection" /> </properties> 

Nonscalar values such as collections and arrays must be serialized to fit in a data storage medium. The serializeAs attribute simply specifies how. As mentioned, acceptable values are String, Xml, Binary, and ProviderSpecific. If the serializeAs attribute is not present on the <properties> definition, the String type is assumed. A collection is normally serialized as XML or in a binary format.

Using Custom Types

You can use a custom type with the ASP.NET personalization layer as long as you mark it as a serializable type. You simply author a class and compile it down to an assembly. The name of the assembly is added to the type information for the profile property:

<properties>     <add name="ShoppingCart"         type="My.Namespace.DataContainer, MyAssem"         serializeAs="Binary" /> </properties> 

The assembly that contains the custom type must be available to the ASP.NET application. You obtain this custom type by placing the assembly in the application's Bin directory or by registering it within the global assembly cache (GAC).

Grouping Properties

The <properties> section can also accept the <group> element. The <group> element allows you to group a few related properties as if they are properties of an intermediate object. The following code snippet shows an example of grouping:

<properties>     ...     <group name="Font">         <add name="Name" type="string" defaultValue="verdana" />         <add name="SizeInPoints" type="int" defaultValue="8" />     </group> </properties> 

The font properties have been declared children of the Font group. This means that from now on any access to Name or SizeInPoints passes through the Font name, as shown here:

string fontName = Profile.Font.Name; 
Note 

Default values are not saved to the persistence layer. Properties declared with a default value make their debut in the storage medium only when the application assigns them a value different from the default one.

Interacting with the Page

To enable or disable profile support, you set the enabled attribute of the <profile> element in the web.config file. If the property is true (the default), personalization features are enabled for all pages. If personalization is disabled, the Profile property isn't available to pages.

Creating the Profile Database

As mentioned earlier, profile works strictly on a per-user basis and is permanently stored. Enabling the feature simply turns any functionality on, but it doesn't create the needed infrastructure for user membership and data storage.

ASP.NET 2.0 comes with an administrative tool the ASP.NET Web Site Administration Tool (WSAT) that is fully integrated in Visual Studio .NET 2005. (See Figure 5-11.) You invoke the tool by choosing ASP.NET Configuration from the Web site menu.

image from book
Figure 5-11: The ASP.NET Web Site Administration Tool, which is used to select the profile provider.

You can use this tool to create a default database to store profile data. The default database is a SQL Server 2005 file named aspnetdb.mdf, which is located in the App_Data special folder of the ASP.NET application. Tables and schema of the database are fixed. The same database contains tables to hold membership and roles information. The use of a membership database with users and roles is important because personalization is designed to be user-specific and because a user ID either a local Windows account or an application-specific logon is necessary to index data.

Profile data has no predefined duration and is permanently stored. It is up to the Web site administrator to delete the information when convenient.

Needless to say, WSAT is just one option not the only one for setting up the profile infrastructure. For example, if you're using a custom provider, the setup of your application is responsible for preparing any required storage infrastructure be it a SQL Server table, an Oracle database, or whatever else. We'll cover the setup of profile providers in the next section.

Working with Anonymous Users

Although user profiles are designed primarily for authenticated users, anonymous users can also store profile data. In this case, though, a few extra requirements must be fulfilled. In particular, you have to turn on the anonymousIdentification feature, which is disabled by default:

<anonymousIdentification enabled="true" /> 

Anonymous identification is a new feature of ASP.NET 2.0. Its purpose is to assign a unique identity to users who are not authenticated and to recognize and treat each of them as an additional registered user.

Note 

Anonymous identification in no way affects the identity of the account that is processing the request. Nor does it affect any other aspects of security and user authentication. Anonymous identification is simply a way to give a "regular" ID to unauthenticated users so that they can be tracked as authenticated, "regular" users.

In addition, to support anonymous identification you must mark properties in the data model with the special Boolean attribute named allowAnonymous. Properties not marked with the attribute are not made available to anonymous users:

<anonymousIdentification enabled="true" /> <profile enabled="true">     <properties>         <add name="BackColor"             type="System.Drawing.Color"             allowAnonymous="true" />         <add name="Links"             type="System.Collections.Specialized.StringCollection"             serializeAs="Xml" />     </properties> </profile> 

In the preceding code snippet, anonymous users can set the background color but not add new links.

Accessing Profile Properties

Before the request begins its processing cycle, the Profile property of the page is set with an instance of a dynamically created class that was created after the user profile defined in the web.config file. When the page first loads, the profile properties are set with their default values (if any) or are empty objects. They are never null. When custom or collection types are used to define properties, assigning default values might be hard. The code just shown defines a string collection object the property Links but giving that a default value expressed as a string is virtually impossible. At run time, though, the Links property won't be null it will equal an empty collection. So how can you manage default values for these properties?

Properties that don't have a default value can be initialized in the Page_Load event when the page is not posting back. Here's how you can do that:

    if (!IsPostBack) {     // Add some default links to the Links property     if (Profile.Links.Count == 0) {         Profile.Links.Add("http://www.contoso.com");         Profile.Links.Add("http://www.northwind.com");     } } 

Let's consider some sample code. Imagine a page like the one in Figure 5-12 that displays a list of favorite links in a panel. Users can customize the links as well as a few other visual attributes, such as colors and font.

image from book
Figure 5-12: Profile information makes the same page display differently for different users.

The profile data is expressed by the following XML:

<profile enabled="true">     <properties>         <add name="BackColor" type="string" />         <add name="ForeColor" type="string" />         <add name="Links"             type="System.Collections.Specialized.StringCollection"/>         <group name="Font">             <add name="Name" type="string" />             <add name="SizeInPoints" type="int" defaultValue="12" />         </group>     </properties> </profile> 

The page uses the profile data to adjust its own user interface, as shown in the following code:

private void ApplyPagePersonalization() {     // Set colors in the panel     InfoPanel.ForeColor = ColorTranslator.FromHtml(Profile.ForeColor);     InfoPanel.BackColor = ColorTranslator.FromHtml(Profile.BackColor);     // Set font properties in panel     InfoPanel.Font.Name = Profile.Font.Name;     InfoPanel.Font.Size = FontUnit.Point(Profile.Font.SizeInPoints);     // Create links     Favorites.Controls.Clear();     if(Profile.Links.Count == 0)         Favorites.Controls.Add(new LiteralControl("No links available."));     else         foreach (object o in Profile.Links) {             HyperLink h = new HyperLink ();             h.Text = o.ToString ();             h.NavigateUrl = o.ToString ();             Favorites.Controls.Add(h);             Favorites.Controls.Add(new LiteralControl("<br />"));         } } 

The ApplyPagePersonalization method is invoked from Page_Load:

protected void Page_Load(object sender, EventArgs e) {     if (!IsPostBack) {         // Initialize profile properties as needed     }     ApplyPagePersonalization(); } 

Initialization is an important phase, but it's strictly application-specific. For example, the first time a user requests the page, no colors are set and no links are defined. Because we enumerate the contents of the collections, an empty collection is just fine and there's no need to further initialize it. (Note that the framework guarantees that reference objects are always instantiated.) What about color properties? If you specify a default value in the web.config file, the framework uses that value to initialize the corresponding property the first time a user requests the page; otherwise, you take care of finding a good default value in Page_Load.

At the end of the request, the contents of the profile object are flushed into the profile storage medium and retrieved the next time the page is invoked. When this happens, no properties result that are uninitialized unless the site administrator has deleted some data offline.

Note that pages that make intensive use of personalization should also provide a user interface to let users modify settings and personalize the visual appearance of the page. The sample page in Figure 5-12 uses a MultiView control to switch between the menu-like view that invites you to edit and the editor-like view shown in Figure 5-13.

image from book
Figure 5-13: The page incorporates a little editor to let users personalize the page's look and feel.

The full source code for the page is available online.

Note 

The personalization data of a page is all set when the Page_Init event fires. ASP.NET 2.0 also defines a Page_PreInit event. When this event arrives, no operation has been accomplished yet on the page, not even the loading of personalization data.

Personalization Events

As mentioned, the personalization data is added to the HTTP context of a request before the request begins its processing route. But which system component is in charge of loading personalization data? ASP.NET 2.0 employs a new HTTP module for this purpose named ProfileModule.

The module attaches itself to a couple of HTTP events and gets involved after a request has been authorized and when the request is about to end. If the personalization feature is off, the module returns immediately. Otherwise, it fires the Personalize event to the application and then loads personalization data from the current user profile. When the Personalize event fires, the personalization data hasn't been loaded yet. Handlers for events fired by an HTTP module must be written to the global.asax file:

void Profile_Personalize(object sender, ProfileEventArgs e) {     ProfileCommon profile = null;     // Exit if it is the anonymous user     if (User == null) return;     // Determine the profile based on the role. The profile database     // contains a specific entry for a given role.     if (User.IsInRole("Administrators"))         profile = (ProfileCommon) ProfileBase.Create("Administrator");     else if (User.IsInRole("Users"))         profile = (ProfileCommon) ProfileBase.Create("User");     else if (User.IsInRole("Guests"))         profile = (ProfileCommon) ProfileBase.Create("Guest");     // Make the HTTP profile module use THIS profile object     if (profile != null)         e.Profile = profile; } 

The personalization layer is not necessarily there for the end user's fun. You should look at it as a general-purpose tool to carry user-specific information. User-specific information, though, indicates information that applies to the user, not necessarily information entered by the user.

The personalization layer employs the identity of the current user as an index to retrieve the proper set of data, but what about roles? What if you have hundreds of users with different names but who share the same set of profile data (such as menu items, links, and UI settings)? Maintaining hundreds of nearly identical database entries is out of the question. But the standard profile engine doesn't know how to handle roles. That's why you sometimes need to handle the Personalize event or perhaps roll your own profile provider.

The code shown previously overrides the process that creates the user profile object and ensures that the returned object is filled with user-specific information accessed through the user role. The static method Create on the ProfileBase class takes the user name and creates an instance of the profile object specific to that user. ProfileCommon is the common name of the dynamically created class that contains the user profile.

The handler of the Personalize event receives data through the ProfileEventArgs class. The class has a read/write member named Profile. When the event handler returns, the profile HTTP module checks this member. If it is null, the module proceeds as usual and creates a profile object based on the user's identity. If not, it simply binds the current value of the Profile member as the profile object of the page.

Migrating Anonymous Data

As mentioned, anonymous users can store and retrieve settings that are persisted using an anonymous unique ID. However, if at a certain point a hitherto anonymous user decides to create an account with the Web site, you might need to migrate to her account all the settings that she made as an anonymous user. This migration doesn't occur automatically.

When a user who has been using your application anonymously logs in, the personalization module fires an event MigrateAnonymous. Properly handled, this global event allows you to import anonymous settings into the profile of an authenticated user. The following pseudocode demonstrates how to handle the migration of an anonymous profile:

void Profile_MigrateAnonymous(object sender, ProfileMigrateEventArgs e) {     // Load the profile of the anonymous user     ProfileCommon anonProfile;     anonProfile = Profile.GetProfile(e.AnonymousId);     // Migrate the properties to the new profile     Profile.BackColor = anonProfile.BackColor;     ... } 

You get the profile for the anonymous user and extract the value of any property you want to import. Next, you copy the value to the profile of the currently logged-on user.

Profile Providers

In ASP.NET 2.0, the profile API is composed of two distinct elements the access layer and the storage layer.

The access layer provides a strongly typed model to get and set property values and also manages user identities. It guarantees that the data is retrieved and stored on behalf of the currently logged-on user.

The second element of the profile system is the data storage. The system uses ad hoc providers to perform any tasks involved with the storage and retrieval of values. ASP.NET 2.0 comes with a profile provider that uses SQL Server 2005 Express as the data engine. If necessary, you can also write custom providers. The profile provider writes data into the storage medium of choice and is responsible for the final schema of the data. A profile provider must be able to either serialize the type (by using XML serialization and binary object serialization, for example) or know how to extract significant information from it.

Important 

As discussed in Chapter 1, an ASP.NET 2.0 provider is defined as a pluggable component that extends or replaces a given system functionality. The profile provider is just one implementation of the ASP.NET 2.0 provider model. Other examples of providers are the membership provider and role manager provider, both of which we'll discuss in Chapter 15. At its core, the provider infrastructure allows customers to change the underlying implementation of some out-of-the-box system functionalities while keeping the top-level interface intact. Providers are relatively simple components with as few methods and properties as possible. Only one instance of the provider exists per application domain.

Configuring Profile Providers

All features, such as user profiling, that have providers should have a default provider. Normally, the default provider is indicated via a defaultProvider attribute in the section of the configuration file that describes the specific feature. By default, if a preferred provider is not specified, the first item in the collection is considered the default.

The default profile provider is named AspNetSqlProfileProvider and uses SQL Server 2005 Express for data storage. Providers are registered in the <providers> section of the configuration file under the main node <profile>, as shown here:

<profile>     <providers>         <add name="AspNetSqlProfileProvider"             connectionStringName="LocalSqlServer" applicationName="/"             type="System.Web.Profile.SqlProfileProvider" />     </providers> </profile> 

The <add> nodes within the <providers> section list all the currently registered providers. The previous code is an excerpt from the machine.config file. Attributes such as name and type are common to all types of providers. Other properties are part of the provider's specific configuration mechanism. Tightly connected with this custom interface is the set of extra properties in this case, connectionStringName and description. The description attribute is simply text that describes what the provider does.

The connectionStringName attribute defines the information needed to set up a connection with the underlying database engine of choice. However, instead of being a plain connection string, the attribute contains the name of a previously registered connection string. For example, LocalSqlServer is certainly not the connection string to use for a local or remote connection to an instance of SQL Server. Instead, it is the name of an entry in the new <connectionStrings> section of the configuration file. That entry contains any concrete information needed to connect to the database.

The LocalSqlServer connection string placeholder is defined in machine.config as follows:

<connectionStrings>     <add name="LocalSqlServer"         connectionString="data source=.\SQLEXPRESS;                          Integrated Security=SSPI;                          AttachDBFilename=|DataDirectory|aspnetdb.mdf;                          User Instance=true"         providerName="System.Data.SqlClient" /> </connectionStrings> 

As you can see, the connection strings refers to an instance of SQL Server named SQLEXPRESS and attaches to the aspnetdb.mdf database located in the application's data directory the App_Data folder.

Structure of AspNetDb.mdf

As a developer, you don't need to know much about the layout of the table and the logic that governs it instead, you're responsible for ensuring that any needed infrastructure is created. To do so, you use the Website menu in Visual Studio .NET 2005 to start the ASP.NET site administration tool.

A view of the tables in the database is shown in Figure 5-14.

image from book
Figure 5-14: A view of the interior of the AspNetDb database and the profile table.

Note that the ASPNetDB database isn't specific to the personalization infrastructure. As you can see in the figure, it groups all provider-related tables, including those for membership, roles, and users. The internal structure of each database is specific to the mission of the underlying provider.

To view and edit the contents of the AspNetDb database (and other databases bound to the application), you can use Server Explorer. You create a new connection to the specified data source and use the menu commands to get what appears in Figure 5-14. The Server Explorer view is integrated with Visual Studio .NET and requires the IDE to be available in the production box. As an alternative for first-aid maintenance, you can consider the add-on tool SQL Server Express Manager a separate download from http://msdn.microsoft.com/vs2005. The tool is a lightweight version of Enterprise Manager and Query Analyzer together. It lets you attach databases and work against them through SQL commands. Note that SQL Server Express Manager lacks UI facilities to attach and detach databases. Use the following command to attach your AspNetDb.mdf file:

CREATE DATABASE AspNetDb ON (FILENAME = 'C:\Inetpub\wwwroot\ProAspNet20\App_Data\aspnetdb.mdf') FOR ATTACH; 

To detach a database, you use the sp_detach built-in stored procedure.

Important 

The default profile provider uses the ADO.NET managed provider for SQL Server and is in no way limited to SQL Express. By changing the connection string, you can make it work with a database handled by the full version of SQL Server 2000 and SQL Server 2005. Using SQL Express is recommended for small applications in Internet scenarios or if you simply want to experiment with the functionality.

Custom Profile Providers

The SQL Server profile provider is good at building new applications and is useful for profile data that is inherently tabular. In many cases, though, you won't start an ASP.NET 2.0 application from scratch but will instead migrate an existing ASP or ASP.NET application. You often already have data to integrate with the ASP.NET profile layer. If this data doesn't get along with the relational model, or if it is already stored in a storage medium other than SQL Server, you can write a custom profile provider.

Profile providers push the idea that existing data stores can be integrated with the personalization engine using a thin layer of code. This layer of code abstracts the physical characteristics of the data store and exposes its content through a common set of methods and properties. A custom personalization provider is a class that inherits ProfileProvider.

Note 

A custom provider can be bound to one or more profile properties through the property's provider attribute:

<properties>       <add name="BackColor" type="string" provider="MyProvider" />      ... </properties> 

As shown in the preceding code, the BackColor property is read and written through the MyProvider provider. It goes without saying that the provider name must correspond to one of the entries in the <providers> section.

 


Programming Microsoft ASP. Net 2.0 Core Reference
Programming Microsoft ASP.NET 2.0 Core Reference
ISBN: 0735621764
EAN: 2147483647
Year: 2004
Pages: 112
Authors: Dino Esposito
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net