Web Parts Personalization Providers

 

Microsoft ASP.NET 2.0 Providers: Introduction
Membership Providers
Role Providers
Site Map Providers
Session State Providers
Profile Providers
Web Event Providers
Web Parts Personalization Providers

Introduction

Web Parts personalization providers provide the interface between Microsoft ASP.NET's Web Parts personalization service and personalization data sources. ASP.NET 2.0 ships with one Web Parts personalization provider: SqlPersonalizationProvider, which stores personalization data in Microsoft SQL Server and Microsoft SQL Server Express databases.

The fundamental job of a Web Parts personalization provider is to provide persistent storage for personalization state—state regarding the content and layout of Web Parts pages—generated by the Web Parts personalization service. Personalization state is represented by instances of System.Web.UI.WebControls.WebParts.PersonalizationState. The personalization service serializes and deserializes personalization state, and presents it to the provider as opaque byte arrays. The heart of a personalization provider is a set of methods that transfer these byte arrays to and from persistent storage.

The Microsoft .NET Framework's System.Web.UI.WebControls.WebParts namespace includes a class named PersonalizationProvider that defines the basic characteristics of a Web Parts personalization provider. PersonalizationProvider is prototyped as follows.

public abstract class PersonalizationProvider : ProviderBase
{
    // Properties
    public abstract string ApplicationName { get; set; }

    // Virtual methods
    protected virtual IList CreateSupportedUserCapabilities() {}
    public virtual PersonalizationScope DetermineInitialScope
        (WebPartManager webPartManager,
        PersonalizationState loadedState) {}
    public virtual IDictionary DetermineUserCapabilities
        (WebPartManager webPartManager) {}
    public virtual PersonalizationState LoadPersonalizationState
        (WebPartManager webPartManager, bool ignoreCurrentUser) {}
    public virtual void ResetPersonalizationState
        (WebPartManager webPartManager) {}
    public virtual void SavePersonalizationState
       (PersonalizationState state) {}

    // Abstract methods
    public abstract PersonalizationStateInfoCollection FindState
        (PersonalizationScope scope, PersonalizationStateQuery query,
        int pageIndex, int pageSize, out int totalRecords);
    public abstract int GetCountOfState(PersonalizationScope scope,
        PersonalizationStateQuery query);
    protected abstract void LoadPersonalizationBlobs
        (WebPartManager webPartManager, string path, string userName,
        ref byte[] sharedDataBlob, ref byte[] userDataBlob);
    protected abstract void ResetPersonalizationBlob
        (WebPartManager webPartManager, string path, string userName);
    public abstract int ResetState(PersonalizationScope scope,
        string[] paths, string[] usernames);
    public abstract int ResetUserState(string path,
        DateTime userInactiveSinceDate);
    protected abstract void SavePersonalizationBlob
        (WebPartManager webPartManager, string path, string userName,
        byte[] dataBlob);
}

The following section documents the implementation of SqlPersonalizationProvider, which derives from PersonalizationProvider.

SqlPersonalizationProvider

SqlPersonalizationProvider is the Microsoft Web Parts personalization provider for SQL Server databases. It stores personalization data, using the schema documented in "Data Schema," and it uses the stored procedures documented in "Data Access." All knowledge of the database schema is hidden in the stored procedures, so that porting SqlPersonalizationProvider to other database types requires little more than modifying the stored procedures. (Depending on the targeted database type, the ADO.NET code used to call the stored procedures might have to change, too. The Microsoft Oracle .NET provider, for example, uses a different syntax for named parameters.)

The ultimate reference for SqlPersonalizationProvider is the SqlPersonalizationProvider source code, which is found in SqlPersonalizationProvider.cs. The sections that follow highlight key aspects of SqlPersonalizationProvider's design and operation.

Provider Initialization

Initialization occurs in SqlPersonalizationProvider.Initialize, which is called one time—when the provider is loaded—by ASP.NET. SqlPersonalizationProvider.Initialize processes the description, applicationName, connectionStringName, and commandTimeout configuration attributes, and throws a ProviderException if unrecognized configuration attributes remain. It also reads the connection string identified by the connectionStringName attribute from the <connectionStrings> configuration section, and caches it in a private field, throwing a ProviderException if the attribute is empty or nonexistent, or if the attribute references a nonexistent connection string.

Data Schema

Web Parts personalization state is inherently scoped by user name and request path. Scoping by user name allows personalization state to be maintained independently for each user. Scoping by path ensures that personalization settings for one page don't affect personalization settings for others. The Web Parts personalization service also supports shared state, which is scoped by request path, but not by user name. (When the service passes shared state to a provider, it passes in a null user name.) When storing personalization state, a provider must take care to key the data by user name and request path, so that it can be retrieved using the same keys later. A provider must also ensure that it stores user-scoped data separately from shared state.

SqlPersonalizationProvider persists per-user personalization state in the aspnet_PersonalizationPerUser table of the provider database, and it persists shared personalization state in the aspnet_PersonalizationAllUsers table. Tables 8-1 and 8-2 document the schemas of these two tables, respectively. State is persisted as a serialized blob in the PageSettings field. The PathId and UserId fields store scoping data, while LastUpdatedDate stores time stamps.

Table 8-1. The aspnet_PersonalizationPerUser table

Column Name Column Type Description
Id uniqueidentifier ID of this record
PathId uniqueidentifier ID of the virtual path to which this state pertains
UserId uniqueidentifier ID of the user to which this state pertains
PageSettings image Serialized personalization state
LastUpdatedDate datetime Date and time state was saved

Table 8-2. The aspnet_PersonalizationAllUsers table

Column Name Column Type Description
PathId uniqueidentifier ID of the virtual path to which this state pertains
PageSettings image Serialized personalization state
LastUpdatedDate datetime Date and time state was saved

The aspnet_PersonalizationPerUser and aspnet_PersonalizationAllUsers tables contain columns named PathID that refer to the column of the same name in the aspnet_Paths table (see Table 8-3). Each entry in the aspnet_Paths table defines one path (for example, ~/MyPage.aspx) for which Web Parts personalization state has been saved. Paths are defined in a separate table, because, for a given path, a personalization provider may be asked to save two types of state: per-user and shared. In that case, both the aspnet_PersonalizationPerUser and aspnet_PersonalizationAllUsers tables will contain entries for the corresponding paths, and each entry will contain a PathId referring to the same entry in aspnet_Paths.

Table 8-3. The aspnet_Paths table

Column Name Column Type Description
ApplicationId uniqueidentifier Application ID
PathId uniqueidentifier Path ID
Path nvarchar(256) Path name
LoweredPath nvarchar(256) Path name (lowercase)

The provider database contains a stored procedure named aspnet_Paths_CreatePath that providers (or stored procedures) can call to retrieve a path ID from the aspnet_Paths table, or to create a new one if the specified path doesn't exist.

Additional Scoping of Personalization Data

In addition to scoping personalization state by user name and path, SqlPersonalizationProvider supports scoping by application name. Websites that register personalization providers with identical applicationName attributes share Web Parts personalization data, whereas websites that register personalization providers with unique applicationNames do not. Due to the page-specific and control-specific nature of personalization data, however, it usually doesn't make sense to use the same applicationName for Web Parts personalization data across different websites.

In support of application-name scoping, SqlPersonalizationProvider records an application ID in the ApplicationId field of each record in the aspnet_Paths table. aspnet_Paths' ApplicationId field refers to the field of the same name in the aspnet_Applications table, and each unique applicationName has a corresponding ApplicationId in that table.

Data Access

SqlPersonalizationProvider performs all database accesses through stored procedures. Table 8-4 lists the stored procedures that it uses.

Table 8-4. Stored procedures used by SqlPersonalizationProvider

Stored Procedure Description
aspnet_PersonalizationAdministration_DeleteAllState Deletes all records from aspnet_PersonalizationAllUsers or aspnet_PersonalizationPerUser corresponding to the specified application ID.
aspnet_PersonalizationAdministration_FindState Retrieves profile data from aspnet_PersonalizationAllUsers or aspnet_PersonalizationPerUser meeting several input criteria.
aspnet_PersonalizationAdministration_GetCountOfState Returns a count of records in the aspnet_PersonalizationAllUsers table with path names matching the specified pattern, or a count of records in the aspnet_PersonalizationPerUser table meeting several input criteria.
aspnet_PersonalizationAdministration_ResetSharedState Resets shared state for the specified page, by deleting the corresponding record from the aspnet_PersonalizationAllUsers table.
aspnet_PersonalizationAdministration_ResetUserState Resets per-user state for the specified user and the specified page, by deleting the corresponding record from the aspnet_PersonalizationPerUser table. Can also delete records, based on the user's last activity date if it falls on or before the specified date.
aspnet_PersonalizationAllUsers_GetPageSettings Retrieves shared state for the specified page from the aspnet_PersonalizationAllUsers table.
aspnet_PersonalizationAllUsers_ResetPageSettings Resets shared state for the specified page, by deleting the corresponding record from the aspnet_PersonalizationAllUsers table.
aspnet_PersonalizationAllUsers_SetPageSettings Saves shared state for the specified page in the aspnet_PersonalizationAllUsers table.
aspnet_PersonalizationPerUser_GetPageSettings Retrieves per-user state for the specified page and the specified user from the aspnet_PersonalizationPerUser table.
aspnet_PersonalizationPerUser_ResetPageSettings Resets per-user state for the specified page and the specified user, by deleting the corresponding record from the aspnet_PersonalizationPerUser table.
aspnet_PersonalizationPerUser_SetPageSettings Saves per-user state for the specified page and the specified user in the aspnet_PersonalizationPerUser table.

Stored procedure names are generally indicative of the SqlPersonalizationProvider methods that call them. For example, ASP.NET calls the default Web Parts personalization provider's SavePersonalizationBlob method to save personalization state, and SavePersonalizationBlob, in turn, calls either aspnet_PersonalizationPerUser_SetPageSettings to save per-user personalization state, or aspnet_PersonalizationAllUsers_SetPageSettings to save shared personalization state, depending on whether it's passed a user name.

Saving Per-User Personalization State

To save Web Parts personalization state for a given user and a given page, ASP.NET calls the SavePersonalizationBlob method of the default Web Parts personalization provider, passing in the user name, the path to the page, and a byte array containing the serialized personalization state. SqlPersonalizationProvider.SavePersonalizationBlob validates the input parameters and calls the stored procedure aspnet_PersonalizationPerUser_SetPageSettings to write the information to the provider database.

aspnet_PersonalizationPerUser_SetPageSettings performs the following actions:

  1. Calls the stored procedure aspnet_Applications_CreateApplication to convert the application name into an application ID, and to create an application record in the aspnet_Applications table if one does not already exist.
  2. Calls the stored procedure aspnet_Paths_CreatePath to convert the path into a path ID, and to create a path record in aspnet_Paths if one does not already exist.
  3. If the user name input to aspnet_PersonalizationPerUser_SetPageSettings doesn't already exist in the aspnet_Users table, calls aspnet_Users_CreateUser to record a new user and return a user ID.
  4. Updates the user's last activity date in the aspnet_Users table with the current date and time.
  5. Either updates an existing record in the aspnet_PersonalizationPerUser table if an entry for the specified user and specified path already exists, or inserts a new one.

Currently, neither SqlPersonalizationProvider.SavePersonalizationBlob nor aspnet_PersonalizationPerUser_SetPageSettings uses transactions to ensure that all changes (that is, creating a new application record, a new path record, and a new user record) are committed to the database as a whole or not at all, leaving open the possibility that the database could be left in an inconsistent state when all these records need to be created for the very first time.

Loading Per-User Personalization State

To load Web Parts personalization state for a given user and a given page, ASP.NET calls the LoadPersonalizationBlobs method of the default Web Parts personalization provider, passing in the user name, the path to the page, and a reference to a byte array through which serialized personalization state is returned. SqlPersonalizationProvider.LoadPersonalizationBlobs validates the input parameters, and calls the stored procedure aspnet_PersonalizationPerUser_GetPageSettings to read the information from the provider database.

aspnet_PersonalizationPerUser_GetPageSettings performs the following actions:

  1. Calls the stored procedure aspnet_Personalization_GetApplicationId to convert the application name input to it into an application ID.
  2. Queries the aspnet_Paths table to convert the path name input to it into a path ID.
  3. Queries the aspnet_Users table to convert the user name input to it into a user ID.
  4. Updates the user's last activity date in the aspnet_Users table with the current date and time.
  5. Queries the PageSettings column of the aspnet_PersonalizationPerUser table for the serialized personalization state.

Saving Shared Personalization State

To save shared Web Parts personalization state for a given page, ASP.NET calls the SavePersonalizationBlob method of the default Web Parts personalization provider, passing in a null user name, the path to the page, and a byte array containing the serialized personalization state. SqlPersonalizationProvider.SavePersonalizationBlob validates the input parameters and, seeing the null user name, calls the stored procedure aspnet_PersonalizationAllUsers_SetPageSettings to write the information to the provider database.

aspnet_PersonalizationAllUsers_SetPageSettings performs the following actions:

  1. Calls the stored procedure aspnet_Applications_CreateApplication to convert the application name into an application ID.
  2. Calls the stored procedure aspnet_Paths_CreatePath to convert the path into a path ID.
  3. Either updates an existing record in the aspnet_PersonalizationAllUsers table if an entry for the specified path already exists, or inserts a new one.

Currently, neither SqlPersonalizationProvider.SavePersonalizationBlob nor aspnet_PersonalizationAllUsers_SetPageSettings uses transactions to ensure that all changes (that is, creating a new application record and a new path record) are committed to the database as a whole or not at all, leaving open the possibility that the database could be left in an inconsistent state when both of these records need to be created for the very first time.

Loading Shared Personalization State

To load shared Web Parts personalization state for a given page, ASP.NET calls the LoadPersonalizationBlobs method of the default Web Parts personalization provider, passing in the path to the page, and a reference to a byte array through which serialized personalization state is returned. SqlPersonalizationProvider.LoadPersonalizationBlobs validates the input parameters and calls the stored procedure aspnet_PersonalizationAllUsers_GetPageSettings to read the information from the provider database.

aspnet_PersonalizationAllUsers_GetPageSettings performs the following actions:

  1. Calls the stored procedure aspnet_Personalization_GetApplicationId to convert the application name input to it into an application ID.
  2. Queries the aspnet_Paths table to convert the path name input to it into a path ID.
  3. Queries the PageSettings column of the aspnet_PersonalizationAllUsers table for the serialized personalization state.

Differences Between the Published Source Code and the .NET Framework's SqlPersonalizationProvider

The published source code for SqlPersonalizationProvider differs from the .NET Framework version in one respect: Declarative and imperative CAS checks were commented out. Because the source code can be compiled standalone, and thus will run as user code rather than trusted code in the global assembly cache, the CAS checks are not necessary.

Return to part 7, Web Event Providers.

© Microsoft Corporation. All rights reserved.