Chapter 29. Personalizing Web Parts

<feature><title>In this Chapter</title> <objective>

Overview of Personalization

</objective>
<objective>

Configuring Personalization

</objective>
<objective>

Creating Personalizable Web Parts

</objective>
<objective>

Administrating Personalization

</objective>
<objective>

Creating Custom Personalization Providers

</objective>
<objective>

Summary

</objective>
</feature>

The heart of the Web Part Framework is personalization. When users interact with a website built with Web Parts, individual users can customize the website according to their personal preferences. They can pick and choose the Web Parts that they want to display in their pages, rearrange the layout of Web Parts to their hearts’ desires, and they can customize the properties of particular Web Parts.

This chapter leaps into the details of personalization. You learn how to enable users to personalize both Web Part pages and individual Web Parts. You also learn how to administer a Web Parts application by pruning stale personalization data. Finally, you learn how to create custom personalization providers. At the end of this chapter, we create both a custom query string personalization provider and an anonymous personalization provider.

Overview of Personalization

When a user modifies the layout or content of a page that contains Web Parts, the personalization changes are scoped to two things: the page and the user. Each page in an application can have different personalization data associated with it. If you change the name of a page, all personalization data that was associated with the page is lost and you must personalize the page again from scratch.

Note

Personalization data is scoped to a page’s application relative path. If you move a Web Part application from one server to another, personalization data is not lost. However, if you rename an application, the personalization data is lost.

Personalization is also scoped to the user. The Web Part Framework supports two types of personalization: User and Shared. By default, when a user customizes a Web Part page, the changes have User scope. Each user of a Web Part application can personalize the same application in different ways.

You can also make Shared scoped hanges to a Web Part application. A Shared scope change, unlike a User scope change, has an effect on all users of a Web Part application. Typically, you want to enable only a select group of administrators to make Shared scope changes to an application.

Shared and User scoped personalization data is merged when a user requests a page. For example, an administrator can add a standard set of Web Parts to a page. A particular user can add an additional set of Web Parts. When the page is displayed, both sets of Web Parts are displayed.

So how is all this personalization data stored? The Web Part framework uses the provider model, so it is really up to you. Later in this chapter, we’ll extend the ASP.NET Framework with our own custom personalization providers.

The default and only personalization provider included in the ASP.NET framework is the SqlPersonalizationProvider. This provider stores personalization data in two database tables: aspnet_PersonalizationAllUsers and aspnet_PersonalizationPerUser. The first table contains the personalization data that is scoped to all users, and the second table contains the personalization data that is scoped to a particular user. By default, these database tables are located in the ASPNETDB.mdf SQL Server 2005 Express database, located in your application’s APP_Data folder.

Using the WebPartPersonalization Class

Under the covers, the WebPartPersonalization class is responsible for all of the low-level operations related to personalization. This class acts as a bridge between the WebPartManager control and a particular personalization provider.

The WebPartPersonalization class is exposed by the WebPartManager control’s Personalization property. You access the properties and methods of the WebPartPersonalization class through the WebPartManager control.

The WebPartPersonalization class has a number of useful properties:

  • CanEnterSharedScopeUse this property to determine whether the current user can make changes to the page that have Shared personalization scope (has an effect on all users).

  • EnabledUse this property to disable personalization for the current page.

  • HasPersonalizationStateUse this property to determine whether any personalization data is associated with the current page and the current user, given the current personalization scope.

  • InitialScopeUse this property to place a page in either Shared or User personalization scope.

  • IsEnabledUse this property to determine whether personalization is currently enabled for this page.

  • IsModifiedUse this property to determine whether the current user can make changes to personalization data.

  • ProviderNameUse this property to retrieve or set the name of the personalization provider.

  • ScopeUse this property to determine the current personalization scope (User or Shared).

Furthermore, the WebPartPersonalization class has two important methods:

  • ResetPersonalizationStateDeletes personalization data associated with the current page and the current user, given the current personalization scope.

  • ToggleScopeToggles the current personalization scope between User personalization scope and Shared personalization scope (or vice versa).

Creating a Personalization Manager

When working with personalization, it helps to see exactly how personalization data is being stored. In the examples in this section, we take advantage of the properties and methods of the WebPartPersonalization class to create a Personalization Manager. The Personalization Manager is contained in Listing 29.1.

Example 29.1. PersonalizationManager.ascx

<%@ Control Language="VB" ClassName="PersonalizationManager" %>
<script runat="server">

    ''' <summary>
    ''' Display Personalization Information
    ''' </summary>
    Private Sub Page_PreRender()
        Dim wpm As WebPartManager = WebPartManager.GetCurrentWebPartManager(Page)
        lblCurrentScope.Text = wpm.Personalization.Scope.ToString()
        lblIsModifiable.Text = wpm.Personalization.IsModifiable.ToString()
        lblCanEnterSharedScope.Text = wpm.Personalization.CanEnterSharedScope.ToString()
        lblHasPersonalizationState.Text =  wpm.Personalization.HasPersonalizationState.ToString()
        lnkToggleScope.Visible = wpm.Personalization.CanEnterSharedScope
    End Sub

    ''' <summary>
    ''' Switches to Shared Scope
    ''' </summary>
    Protected Sub lnkToggleScope_Click(ByVal sender As Object, ByVal e As EventArgs)
        Dim wpm As WebPartManager = WebPartManager.GetCurrentWebPartManager(Page)
        wpm.Personalization.ToggleScope()
    End Sub

    ''' <summary>
    ''' Deletes Personalization data
    ''' </summary>
    Protected Sub lnkReset_Click(ByVal sender As Object, ByVal e As EventArgs)
        Dim wpm As WebPartManager = WebPartManager.GetCurrentWebPartManager(Page)
        wpm.Personalization.ResetPersonalizationState()
    End Sub
</script>
<div class="personalizationManager">
Current Scope:
<asp:Label
    id="lblCurrentScope"
    Runat="server" />
Can Modify State:
<asp:Label
    id="lblIsModifiable"
    Runat="server" />
Can Enter Shared Scope:
<asp:Label
    id="lblCanEnterSharedScope"
    Runat="server" />
Has Personalization State:
<asp:Label
    id="lblHasPersonalizationState"
    Runat="server" />
<span>
<asp:LinkButton
    id="lnkToggleScope"
    Text="Toggle Scope"
    OnClick="lnkToggleScope_Click"
    Runat="server" />
</span>
<asp:LinkButton
    id="lnkReset"
    Text="Reset Personalization"
    Runat="server" OnClick="lnkReset_Click" />
</div>

The Personalization Manager displays an information bar across the top of a page (see Figure 29.1). The bar displays the values of the following properties:

  • Current ScopeDisplays whether the page is in User or Shared scope personalization mode.

  • Can Modify StateDisplays whether the user can modify the personalization state associated with the page.

  • Can Enter Shared ScopeDisplays whether the user can enter Shared Personalization scope.

  • Has Personalization StateDisplays whether the current page, given the current personalization scope, has personalization data associated with it.

Viewing the Personalization Manager.

Figure 29.1. Viewing the Personalization Manager.

The Personalization Manager includes links to invoke the following methods:

  • Toggle ScopeClicking this link switches the page from User to Shared personalization scope (or back again). This link appears only when the user can enter Shared personalization scope.

  • Reset PersonalizationClicking this link removes the personalization data associated with the current personalization scope. Clicking this link in User scope personalization mode removes all User scoped personalization data, and clicking the link in Shared scope personalization mode removes all Shared scoped personalization data.

You can use the Personalization Manager in any Web Part page. For example, you can experiment with the Personalization Manager with the page in Listing 29.2.

Example 29.2. ShowPersonalizationManager.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="PersonalizationManager"
    Src="~/PersonalizationManager.ascx" %>
<%@ Register TagPrefix="user" TagName="FirstSimplePart"
    Src="~/FirstSimplePart.ascx" %>
<%@ Register TagPrefix="user" TagName="SecondSimplePart"
    Src="~/SecondSimplePart.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected  Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs)
        WebPartManager1.DisplayMode = WebPartManager1.DisplayModes(e.Item.Text)
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .personalizationManager
        {
            border:dotted 2px orange;
            padding:5px;
            background-color:White;
            font:12px Arial, Sans-Serif;
        }
        .personalizationManager span
        {
            padding-right:10px;
            margin-right:10px;
            border-right:solid 1px black;
        }
        .personalizationManager a
        {
            text-decoration:none;
        }
        .column
        {
            float:left;
            width:40%;
            height:200px;
            margin-right:10px;
            border:solid 1px black;
            background-color: white;
        }
        .menu
        {
            margin:5px 0px;
        }
        html
        {
            background-color:#eeeeee;
        }
    </style>
    <title>Show Personalization Manager</title>
</head>
<body>
    <form id="form1" runat="server">
    <user:PersonalizationManager
        id="PersonalizationManager1"
        Runat="Server" />

    <asp:WebPartManager
        id="WebPartManager1"
        Runat="server" />

        <asp:Menu
            id="Menu1"
            OnMenuItemClick="Menu1_MenuItemClick"
            Orientation="Horizontal"
            CssClass="menu"
            Runat="server">
            <Items>
            <asp:MenuItem Text="Browse" />
            <asp:MenuItem Text="Design" />
            </Items>
        </asp:Menu>

        <asp:WebPartZone
            id="WebPartZone1"
            CssClass="column"
            Runat="server">
            <ZoneTemplate>
            <user:FirstSimplePart
                id="FirstSimplePart1"
                Title="First Web Part"
                Description="Our first simple Web Part"
                Runat="server" />
            <user:SecondSimplePart
                id="SecondSimplePart1"
                Title="Second Web Part"
                Description="Our second simple Web Part"
                Runat="server" />
            </ZoneTemplate>
        </asp:WebPartZone>

        <asp:WebPartZone
            id="WebPartZone2"
            CssClass="column"
            Runat="server" />

    </form>
</body>
</html>

When you first open the page in Listing 29.2, the Personalization Manager displays the fact that no personalization data is associated with the page. If you click the Design link, and re-arrange the Web Parts in the page, you create new personalization data. You can click the Reset Personalization link at any time to clear away any personalization data associated with the page.

Configuring Personalization

To this point, we have relied on User scoped personalization exclusively. All personalization data has been scoped to a particular user. In this section, you’ll learn how to enable Shared personalization scope so that you can enable administrators to make changes to an application for everyone.

You’ll also learn how to configure the SqlPersonalization provider to store personalization data in a particular database in your computer network.

Configuring User and Shared Scope Personalization

To enable User and Shared scope personalization, you must authorize the group of users allowed to make personalization changes. By default, all users are allowed to make User personalization changes and no users are allowed to make Shared personalization changes.

The default personalization configuration settings are contained in the root Web.Config file located at the following path:

WindowsMicrosoft.NETFrameworkv2.0.xxxxxconfig

The default authorization section is contained in Listing 29.3.

Example 29.3. Web.Config

<webParts>
  <personalization>
  <authorization>
    <deny users="*" verbs="enterSharedScope" />
    <allow users="*" verbs="modifyState" />
  </authorization>
  </personalization>
</webParts>

The first authorization rule prevents any user from entering Shared authorization scope. The second authorization rule enables any user to personalize Web Part pages.

To authorize an administrator to make Shared personalization changes, you need to override these settings in the default Web configuration file. If you add the configuration file in Listing 29.4 to the root of your application, then anyone who is a member of the Administrators role can make Shared personalization changes.

Example 29.4. Web.Config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.web>
     <webParts>
      <personalization>
        <authorization>
          <allow users="*" verbs="modifyState" />
          <allow roles="Administrators" verbs="enterSharedScope" />
        </authorization>
      </personalization>
    </webParts>
  </system.web>
</configuration>

CD Note

The Web.Config file in Listing 29.4 is saved with the name Web.Config_listing4 on the CD so that it does not interfere with the other code samples in this chapter.

After you authorize a user or role to enter Shared personalization scope, you can place a page into Shared personalization mode in either of two ways. First, you can take advantage of the InitialScope property of the WebPartPersonalization class. If you want a page to open in Shared personalization mode by default, then you can declare the WebPartManager control in the page like this:

<asp:WebPartManager
  id="WebPartManager1"
  Personalization-InitialScope="Shared"
  Runat="server" />

This WebPartManager control declaration causes the page to enter Shared scope personalization mode by default. If a user requests the page and the user is authorized to enter Shared scope, then changes made to the page have Shared scope. If the user is not authorized to make Shared scope changes, then the page remains in User scope personalization mode (no exception is thrown).

Note

You cannot modify the IntialScope property after the Page PreInit event. What this means, in practice, is that you need to set this property declaratively. The ToggleScope() method gets around this limitation by automatically performing a Server.Transfer() back to the same page.

An alternate and in many situations better way to change personalization scope is to take advantage of the ToggleScope() method. Calling this method switches the page between User and Shared personalization scope.

The Personalization Manager created in Listing 29.1 includes a link that invokes the ToggleScope() method. If you want to experiment with Shared personalization scope, you can add the Personalization Manager to a Web Part Page and toggle between User and Shared scope.

Configuring the Personalization Database

By default, all personalization data is saved in a SQL Server 2005 Express database named ASPNETDB.mdf, located in your application’s APP_Data folder. If you want to store personalization data in another SQL Server database on your network (for example, a SQL Server 2000 or SQL Server 2005 database) then you need to do two things:

  1. You need to set up the new database.

  2. You need to modify your application’s web configuration file.

First, you need to add the necessary database objects for personalization to the new database. The ASP.NET Framework includes a command-line tool named aspnet_regsql, which automatically installs the necessary objects. This tool is located at the following path:

WindowsMicrosoft.NETFrameworkv2.0.xxxxxaspnet_regsql.exe

If you run the tool without any parameters, the tool displays a wizard that walks you through the steps required for configuring a database for personalization (see Figure 29.2).

The ASP.NET SQL Server Setup Wizard.

Figure 29.2. The ASP.NET SQL Server Setup Wizard.

Alternatively, you can execute a set of SQL Server batch files directly against a database. This is useful when you don’t want to set up the .NET Framework on the server hosting your database. The same folder that contains the aspnet_regsql.exe tool contains the following four scripts:

  • InstallCommon.sqlInstalls the database objects that are used by several ASP.NET services such as the aspnet_Users table. Run this script first.

  • InstallPersonalization.sqlInstalls the database objects particular to Web Part personalization. Run this script after executing InstallCommon.sql.

  • UninstallCommon.sqlRemoves the objects added by InstallCommon.sql.

  • UninstallPersonalization.sqlRemoves the objects added by InstallPersonalization.sql.

After you add the necessary database objects for personalization, you can point any of your ASP.NET applications at the new database. For example, the Web configuration file in Listing 29.5 causes an application to store personalization data in a SQL Server database named AppData located on a server named DataServer.

Example 29.5. Web.Config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <connectionStrings>
    <add
      name="DataServer"
      connectionString="Server=DataServer;Trusted_Connection=true;
      Database=AppData"/>
  </connectionStrings>

  <system.web>
    <webParts>
      <personalization defaultProvider="MyPersonalizationProvider">
        <providers>
          <add
            name="MyPersonalizationProvider"
            type="System.Web.UI.WebControls.WebParts.SqlPersonalizationProvider"
            connectionStringName="DataServer" />
        </providers>
        <authorization>
          <allow users="*" verbs="modifyState" />
          <allow roles="Administrators" verbs="enterSharedScope" />
        </authorization>
      </personalization>
    </webParts>
  </system.web>
</configuration>

CD Note

The Web.Config file in Listing 29.5 is saved with the name Web.Config_listing5 on the CD so that it does not interfere with the other code samples in this chapter.

The web configuration file in Listing 29.5 contains a <webParts> section that contains a <personalization> sub-section. The defaultProvider attribute points to a provider named MyPersonalizationProvider defined in the <providers> section. This provider uses the SqlPersonalization provider and uses a database connection string named DataServer.

The DataServer connection string is defined in the <connectionStrings> configuration section at the top of the configuration file. This connection string points to a server named DataServer and a database named AppData. You can, of course, provide a connection string for any database in your network.

Creating Personalizable Web Parts

Users can edit Web Part properties at runtime. For example, you might want to create a Web Part that displays database data. You can enable users to modify the SQL select query that returns the database records.

The Web Part Framework automatically saves the value of any property marked with the Personalizable attribute. By default, a personalizable property is scoped to a particular user. In other words, each user can personalize a property in different ways. However, you also have the option of creating a personalizable property that can be modified only in Shared user scope.

The Web Part in Listing 29.6, the DataPart Web Part, enables you to display records from any database table. The Web Part includes one personalizable property that has User scope—the SelectCommand property—and one property that has Shared scope—the ConnectionString property.

Example 29.6. DataPart.ascx

<%@ Control Language="VB" ClassName="DataPart" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">

    Private _connectionString As String =  String.Empty
    Private _selectCommand As String =  String.Empty

    <Personalizable(PersonalizationScope.Shared)> _
    <WebBrowsable> _
    Public Property ConnectionString() As String
        Get
                 Return _connectionString
        End Get
        Set (ByVal Value As String)
                 _connectionString = value
        End Set
    End Property

    <Personalizable> _
    <WebBrowsable> _
    Public Property SelectCommand() As String
        Get
                 Return _selectCommand
        End Get
        Set (ByVal Value As String)
                 _selectCommand = value
        End Set
    End Property

    Private  Sub Page_PreRender()
        If _connectionString <> String.Empty And_selectCommand <> String.Empty Then
            Try
                Dim dad As SqlDataAdapter =  New SqlDataAdapter(_selectCommand,_connectionString)
                Dim dst As DataSet =  New DataSet()
                dad.Fill(dst)
                grdData.DataSource = dst
                grdData.DataBind()
            Catch e As Exception
                lblError.Text = e.Message
            End Try
        End If
    End Sub

</script>

<asp:Label
    id="lblError"
    EnableViewState="false"
    Runat="server" />

<asp:GridView
    id="grdData"
    Runat="server" />

Notice that the first property in Listing 29.6, the ConnectionString property, is decorated with a Personalizable attribute that includes a PersonalizationScope.Shared parameter. This property can be edited only by an administrator who can enter Shared personalization mode.

The second property, SelectCommand, is also decorated with the Personalizable attribute. This property can be modified in either Shared or User personalization mode. Modifying the property in Shared personalization mode provides the property with a default value for everyone. (Individual users can override the default value.)

You can experiment with the DataPart Web Part with the page in Listing 29.7. This page includes the Personalization Manager so you can toggle between User and Shared scope.

Example 29.7. ShowDataPart.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="DataPart" Src="~/DataPart.ascx" %>
<%@ Register TagPrefix="user" TagName="PersonalizationManager"
    Src="~/PersonalizationManager.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected  Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs)
        WebPartManager1.DisplayMode = WebPartManager1.DisplayModes(e.Item.Text)
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .personalizationManager
        {
            border:dotted 2px orange;
            padding:5px;
            background-color:White;
            font:12px Arial, Sans-Serif;
        }
        .personalizationManager span
        {
            padding-right:10px;
            margin-right:10px;
            border-right:solid 1px black;
        }
        .personalizationManager a
        {
            text-decoration:none;
        }
        .column
        {
            float:left;
            width:30%;
            height:200px;
            margin-right:10px;
            border:solid 1px black;
            background-color: white;
        }
        .menu
        {
            margin:5px 0px;
        }
        html
        {
            background-color:#eeeeee;
        }
    </style>
    <title>Show Data Part</title>
</head>
<body>
    <form id="form1" runat="server">
    <user:PersonalizationManager
        id="PersonalizationManager1"
        Runat="Server" />

    <asp:WebPartManager
        id="WebPartManager1"
        Runat="server" />

        <asp:Menu
            id="Menu1"
            OnMenuItemClick="Menu1_MenuItemClick"
            Orientation="Horizontal"
            CssClass="menu"
            Runat="server">
            <Items>
            <asp:MenuItem Text="Browse" />
            <asp:MenuItem Text="Design" />
            <asp:MenuItem Text="Edit" />
            </Items>
        </asp:Menu>

        <asp:WebPartZone
            id="WebPartZone1"
            CssClass="column"
            Runat="server">
            <ZoneTemplate>
            <user:DataPart
                id="DataPart1"
                Title="Data Part"
                Description="Displays database records"
                Runat="server" />
            </ZoneTemplate>
        </asp:WebPartZone>

        <asp:WebPartZone
            id="WebPartZone2"
            CssClass="column"
            Runat="server" />

        <asp:EditorZone
            id="EditorZone1"
            CssClass="column"
            Runat="server">
            <ZoneTemplate>
            <asp:PropertyGridEditorPart
                id="PropertyGridEditorPart1"
                Runat="server" />
            </ZoneTemplate>
        </asp:EditorZone>
    </form>
</body>
</html>

After you open the page in Listing 29.7, you can edit the properties of the DataPart Web Part by clicking the Edit link. The PropertyGridEditorPart control automatically renders a property sheet, which enables you to modify the values of the ConnectionString and SelectCommand properties (see Figure 29.3).

Editing the ConnectionString and SelectCommmand properties.

Figure 29.3. Editing the ConnectionString and SelectCommmand properties.

The ConnectionString property appears only when you enter Shared personalization scope by clicking the Toggle Scope link in the Personalization Manager control’s information bar. The SelectCommand property, on the other hand, can be altered in either Shared or User personalization mode.

CD Note

The CD includes a sample database in the App_Data folder named MyDatabase.mdf, which includes a Movie database table. You can connect to this database with the following connection string:

Server=.SQLExpress;Trusted_Connection=true;
AttachDbFileName=|DataDirectory|MyDatabase.mdf;User Instance=true

And execute the following SQL SELECT statement:

SELECT * FROM Movies

Working with Complex Personalizable Properties

In the previous section, you learned how to use the Personalizable attribute with properties that represent simple types such as Strings and Integers. You also can use the Web Part Framework to automatically persist the values of properties that represent more complex types such as ArrayLists or custom classes.

Behind the scenes, the Web Part Framework uses the ObjectStateFormatter class to serialize and deserialize the values of Web Part properties. This is a powerful class. It can serialize the state of any class that can be represented statically.

Note

You should not use personalization with a property that returns an instance of a class defined in the App_Code folder. If you make changes to the App_Code folder, the contents of the folder are automatically recompiled into a new assembly. Because the assembly name changes with each recompilation, the Web Part Framework cannot automatically serialize and deserialize classes defined in the assembly.

However, one important limitation of the Web Part Framework relates to complex properties. The Web Part Framework can detect changes to simple properties automatically, but the framework cannot detect changes made to more complex properties automatically. In general, the Web Part Framework can detect changes to immutable properties, but not changes made to mutable properties.

Note

A mutable type is a type that has properties or fields that can change after it is instantiated. Most reference types are mutable. Most value types are immutable.

For example, if you attempt to use the Personalizable attribute with a property that returns an ArrayList, you don’t get an exception, but the state of the ArrayList is not saved. The Web Part Framework fails to save the state of the property because the Web Part Framework cannot detect when the ArrayList has changed.

There are two ways around this limitation. The next section in this chapter discusses how you can take advantage of the IPersonalizable interface to take a more hands-on approach to personalization state management. When you implement the IPersonalizable interface, you can indicate exactly when you want the Web Part Framework to save changes to a Web Part’s properties.

This section, however, explores a simpler option. The WebPart class includes a method named the SetPersonalizationDirty() method. There is both a shared (static) and an instance version of this method, so you can use it either when working with a Web Part created from a User Control or when working with a “True” Web Part derived from the base WebPart class.

For example, the Web Part in Listing 29.8—the FirstTaskListPart Web Part—enables you to create and save a task list (see Figure 29.4). The list of tasks is represented by an ArrayList.

A personalizable task list.

Figure 29.4. A personalizable task list.

CD Note

You can view the FirstTaskListPart by opening the ShowFirstTaskListPart.aspx page included on the CD that accompanies this book.

Example 29.8. FirstTaskListPart.ascx

<%@ Control Language="VB" ClassName="FirstTaskListPart" %>
<script runat="server">

    Private _tasks As ArrayList = Nothing

    <Personalizable()> _
    Public Property Tasks() As ArrayList
        Get
            Return _tasks
        End Get
        Set(ByVal Value As ArrayList)
            _tasks = value
        End Set
    End Property

    Private Sub Page_PreRender()
        grdTasks.DataSource = _tasks
        grdTasks.DataBind()
    End Sub

    Protected Sub btnAdd_Click(ByVal sender As Object, ByVal e As EventArgs)
        If _tasks Is Nothing Then
            _tasks = New ArrayList()
        End If
        _tasks.Add(txtNewTask.Text)
        WebPart.SetPersonalizationDirty(Me)
    End Sub
</script>

<asp:GridView
    id="grdTasks"
    Runat="server" />

<hr>
<b>New Task:</b>
<asp:TextBox
    id="txtNewTask"
    Runat="server" />
<asp:Button
    id="btnAdd"
    Text="Add"
    OnClick="btnAdd_Click"
    Runat="server" />

Notice that the WebPart.SetPersonalizationDirty() method is called after a new item is added to the ArrayList. If you neglected to call this method, then you would never be able to add more than a single item to the Task List.

Also, it is important to notice that the _tasks variable used to represent the tasks is initially set to Nothing (null). The variable is initialized like this:

Private _tasks As ArrayList = Nothing

It is important that you initialize a personalizable property that represents a reference type with the value Nothing. The very first value assigned to a personalizable property is considered the default value. The Web Part Framework compares the current value against the default value and if there are no changes, the framework does not update the saved personalization data.

Imagine that you had initialized the property like this:

Private _tasks As ArrayList = New ArrayList()

In that case, because an ArrayList is a reference type, the Web Part Framework would never detect a change in the property even when new items have been added to the ArrayList. (This is true even when you use the WebPart.SetPersonalizationDirty() method to warn the framework that there have been changes.)

Using the IPersonalizable Interface

In most cases, using the Personalizable attribute to mark the Web Part properties that you want to save automatically works fine. However, the Personalizable attribute does have some limitations:

  • A Personalizable property must be public.

  • A Personalizable property must have both a public get and set accessor.

  • A Personalizable property cannot have an indexer or parameter.

  • A Personalizable property is ignored in a nested control.

If you encounter one of these limitations, then you have no choice but to implement the IPersonalizable interface. When you implement this interface, you are responsible for selecting the data that you want to save.

The IPersonalizable interface includes one property and two methods that you must implement:

  • IsDirtyReturn the value True from this property when the Web Part Framework calls the Save() method.

  • Load()This method loads personalization state information.

  • Save()This method saves personalization state information.

For example, Listing 29.9 contains a task list Web Part that implements the IPersonalizable interface named SecondTaskListPart.ascx.

Example 29.9. SecondTaskListPart.ascx

<%@ Control Language="VB" ClassName="SecondTaskListPart" %>
<%@ Implements Interface="System.Web.UI.WebControls.WebParts.IPersonalizable" %>
<script runat="server">

    Private _tasks As ArrayList = New ArrayList()
    Private _isDirty As Boolean = False

    Public Property Tasks() As ArrayList
        Get
            Return _tasks
        End Get
        Set(ByVal Value As ArrayList)
            _tasks = value
        End Set
    End Property

    Public ReadOnly Property IsDirty() As Boolean Implements IPersonalizable.IsDirty
        Get
            Return _isDirty
        End Get
    End Property

    Public Sub Save(ByVal state As PersonalizationDictionary) Implements IPersonalizable.Save
        Dim wpm As WebPartManager = WebPartManager.GetCurrentWebPartManager(Page)
        state.Add("tasks", New PersonalizationEntry(_tasks, wpm.Personalization.Scope))
    End Sub

    Public Sub Load(ByVal state As PersonalizationDictionary) Implements IPersonalizable.Load
        _tasks = CType(state("tasks").Value, ArrayList)
    End Sub

    Private Sub Page_PreRender()
        grdTasks.DataSource = _tasks
        grdTasks.DataBind()
    End Sub

    Protected Sub btnAdd_Click(ByVal sender As Object, ByVal e As EventArgs)
        _tasks.Add(txtNewTask.Text)
        _isDirty = True
    End Sub
</script>
<asp:GridView
    id="grdTasks"
    Runat="server" />

<hr>
<b>New Task:</b>
<asp:TextBox
    id="txtNewTask"
    Runat="server" />
<asp:Button
    id="btnAdd"
    Text="Add"
    OnClick="btnAdd_Click"
    Runat="server" />

CD Note

You can view the SecondTaskListPart Web Part with the ShowSecondTaskList.aspx page on the CD that accompanies this book.

Notice that the Web Part in Listing 29.9 includes an <%@ Implements %> directive and that the Web Part implements the IPersonalizable interface.

The body of the Web Part contains a GridView control, a TextBox control, and a Button control. When a user enters a new task description in the TextBox and clicks the button, the btnAdd_Click() method executes. This method adds the new task to the ArrayList and marks the Web Part as dirty.

Each time the Web Part is loaded in the page, the Load() method is called and the task list is returned from the underlying personalization data store. Whenever the Web Part is marked as dirty—after a user enters a new task—the Save() method is called. The Web Part framework calls this method before saving the personalization data.

Notice that both the Load() and Save() methods use a PersonalizationDictionary, which contains instances of the PersonalizationEntry class. Each PersonalizationEntry represents the information being saved and the personalization scope associated with the information being saved (User or Shared). In the Save() method, the current WebPartManager control is used to determine the page’s current personalization scope.

Administrating Personalization

A public website that uses Web Parts might have thousands of members. Each member, potentially, could personalize multiple pages in the website.

Most public websites experience significant churn. A person registers at the website, plays around with it for a few minutes, and then leaves without ever being seen again. Saving personalization data for inactive users could be a huge waste of resources.

The Web Part Framework includes a PersonalizationAdministration class. This class includes several valuable methods for identifying and pruning inactive personalization data. Here is a list of the methods supported by this class:

  • FindInactiveUserStateReturns a collection of User personalization state information when supplied with path, username, and date parameters. This method supports wildcards in its parameters.

  • FindSharedStateReturns a collection of Shared personalization state information when supplied with a path. This method supports wildcards in its path parameter.

  • FindUserStateReturns a collection of User personalization state information when supplied with a path and username. This method supports wildcards in its parameters.

  • GetAllInactiveUserStateReturns a collection of User personalization state information when supplied with a date.

  • GetAllStateReturns a collection of personalization state information when supplied with a personalization scope.

  • GetCountOfInactiveUserStateReturns the number of user personalization items older than the supplied date parameter.

  • GetCountOfStateReturns the number of user personalization items matching a particular personalization scope.

  • GetCountOfUserStateReturns the number of user personalization items matching a certain username. This method supports wildcards in the username parameter.

  • ResetAllStateDeletes personalization information matching a particular personalization scope.

  • ResetInactiveUserStateDeletes User personalization data older than the supplied date parameter.

  • ResetSharedStateDeletes Shared state information that matches the supplied path parameter.

  • ResetStateDeletes state information that matches the contents of the supplied parameter, which represents a collection of state information.

  • ResetUserStateDeletes state information that matches either the supplied username or path parameters.

The ResetAllState() method is the nuclear bomb of personalization administration methods. You can use this method to blow away all User and Shared state information for all users.

The other methods are useful for pruning stale state information. The page in Listing 29.10 illustrates how you can retrieve personalization state information for a particular user.

Example 29.10. AdministerPersonalization.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected  Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs)
        GridView1.DataSource = PersonalizationAdministration.FindUserState(Nothing, txtUsername.Text)
        GridView1.DataBind()
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .grid
        {
            font: 14px Arial, Sans-Serif;
        }
        .grid td
        {
            padding:10px;
        }
        .grid th
        {
            padding:10px;
            background-color:orange;
            text-align:left;
        }
    </style>

    <title>Administer Personalization</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblUsername"
        AssociatedControlID="txtUsername"
        Text="Username:"
        Runat="server" />
    <asp:TextBox
        id="txtUsername"
        Runat="server" />
    <asp:Button
        id="btnSubmit"
        Text="Submit"
        OnClick="btnSubmit_Click"
        Runat="server" />

    <hr />
    <asp:GridView
        id="GridView1"
        CssClass="grid"
        Runat="server" />

    </div>
    </form>
</body>
</html>

If you enter a username in the form contained in Listing 29.10 and click the Submit button, matching personalization items are displayed by the GridView control (see Figure 29.5). The FindUserState() method is used to retrieve the matching personalization items.

Displaying personalization data.

Figure 29.5. Displaying personalization data.

Creating Custom Personalization Providers

The Web Part Framework uses the Provider Model to save personalization information. The default and only Personalization Provider included with the framework is the SqlPersonalizationProvider. The beautiful thing about the Provider Model is that if you don’t like anything about the default provider, then you can easily create your own custom provider.

In this section, we create two custom personalization providers: a Query String Personalization Provider and a Anonymous Personalization Provider.

Building a Query String Personalization Provider

Personalization information, by default, is scoped against a particular page path. A user can personalize two different pages in two different ways. However, a user cannot personalize the same page in two different ways.

For some applications, this limitation might present a problem. For example, imagine that you are creating an online store that includes a Product.aspx page. This page displays information on different products, depending on the value of a Query String passed to the page.

Note

Another approach to solving the problem discussed in this section is to create a VirtualPathProvider. This option is discussed in Chapter 19, “Advanced Navigation.”

The SqlPersonalization provider does not enable you to customize the page differently, depending on the product ID passed to the page. To enable users to personalize different versions of the same product page, you need to create a custom Query String Personalization Provider.

This project contains the following two pages:

  • MovieList.aspxThis page displays a list of movies. Clicking a movie links to the MovieDetails.aspx page, with the movie ID passed as a query string parameter.

  • MovieDetails.aspxThis Web Part page displays information on a particular movie.

The project also includes the following two Web Parts:

  • MoviePart.ascxDisplays details for a particular movie.

  • TextPart.ascxDisplays a block of text.

Finally, the project contains the following two support files:

  • QueryStringPersonalizationProviderThis class implements the custom personalization provider.

  • Web.ConfigThis configuration file configures the custom personalization provider.

All the files listed here are contained on the CD that accompanies the book. In this section, we concentrate on the QueryStringPersonalizationProvider class, which is contained in Listing 29.11.

Example 29.11. QueryStringPersonalizationProvider.vb

Imports System
Imports System.Web
Imports System.Web.UI.WebControls.WebParts

Namespace myControls

    ''' <summary>
    ''' Custom personalization provider which takes into account the
    ''' id query string parameter.
    ''' </summary>
    Public Class QueryStringPersonalizationProvider
        Inherits SqlPersonalizationProvider

        ''' <summary>
        ''' Called when data is saved to the database
        ''' </summary>
        Protected Overrides Sub SavePersonalizationBlob(ByVal webPartManager As  WebPartManager, ByVal path As String, ByVal userName As String, ByVal dataBlob() As Byte)
            Dim queryStringId As String = HttpContext.Current.Request("id")
            If Not queryStringId Is Nothing Then
                path += "?id=" + queryStringId
            End If
            MyBase.SavePersonalizationBlob(webPartManager, path, userName, dataBlob)
        End Sub

        ''' <summary>
        ''' Called when data is loaded from the database
        ''' </summary>
        Protected Overrides Sub LoadPersonalizationBlobs(ByVal webPartManager As WebPartManager, ByVal path As String, ByVal userName As String, ByRef sharedDataBlob() As Byte, ByRef userDataBlob() As Byte)
            Dim queryStringId As String = HttpContext.Current.Request("id")
            If Not queryStringId Is Nothing Then
                path += "?id=" + queryStringId
            End If
            MyBase.LoadPersonalizationBlobs(webPartManager, path, userName,  sharedDataBlob, userDataBlob)
        End Sub

        ''' <summary>
        ''' Called when a user's personalization data is reset
        ''' </summary>
        Protected Overrides Sub ResetPersonalizationBlob(ByVal webPartManager As  WebPartManager, ByVal path As String, ByVal userName As String)
            Dim queryStringId As String = HttpContext.Current.Request("id")
            If Not queryStringId Is Nothing Then
                path += "?id=" + queryStringId
            End If
            MyBase.ResetPersonalizationBlob(webPartManager, path, userName)
        End Sub

    End Class
End Namespace

The class in Listing 29.11 overrides three methods of the base SqlPersonalizationProvider class: the SavePersonalizationBlob(), LoadPersonalizationBlob(), and ResetPersonalizationBlob() methods. The Blob, in this context, refers to the serialized blob of personalization data.

In each of these methods, the value of the query string parameter named ID is added to the path associated with the state data being saved. In other words, the state data is scoped to the path and ID query string parameter.

After you add the class in Listing 29.11 to your application’s App_Code folder, you need to configure the custom personalization provider. The Web configuration file in Listing 29.12 configures the QueryStringPersonalizationProvider as the application’s default personalization provider.

Example 29.12. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <connectionStrings>
    <add
      name="Northwind"
      connectionString="Server=localhost;Trusted_Connection=true;
Database=Northwind"/>
  </connectionStrings>
    <system.web>
      <webParts>
        <personalization
          defaultProvider="QueryStringPersonalizationProvider">
          <providers>
            <add
              name="QueryStringPersonalizationProvider"
              type="myControls.QueryStringPersonalizationProvider"
              connectionStringName="localSQLServer" />
          </providers>
        </personalization>
      </webParts>
    </system.web>
</configuration>

CD Note

The Web.Config file in this section is named Web.Config_listing12 on the CD so that it does not interfere with the other code samples in this chapter.

You can test the QueryStringPersonalizatonProvider by opening the MovieList.aspx page on the CD that accompanies this book in your browser. When you click on a movie title, you are linked to the MovieDetails.aspx page, which displays a particular movie. Notice that you can personalize each version of the MovieDetails.aspx page differently. For example, you can enter different text in the TextPart control for each movie (see Figure 29.6 and Figure 29.7), even though the different movies are displayed by the same page.

Displaying Star Wars movie details.

Figure 29.6. Displaying Star Wars movie details.

Displaying Jaws movie details.

Figure 29.7. Displaying Jaws movie details.

Building an Anonymous Personalization Provider

The default SqlPersonalizationProvider stores personalization data for a user only when the user is authenticated. This requirement makes sense. Typically, you do not want to store personalization data for every random stranger who visits your website.

However, there are situations in which you might want to enable anonymous users to personalize a Web Part application. For example, you might want to create a customizable portal page, such as those used for My MSN or My Yahoo, and not require users to register at your website prior to performing the customization.

Fortunately, modifying the existing SqlPersonalizationProvider to support anonymous users is not that difficult because another part of the ASP.NET Framework already includes the infrastructure for identifying anonymous users. The ASP.NET Framework supports a feature called Anonymous Identification, which is used to support anonymous ASP.NET Profiles.

Note

The Profile class is discussed in Chapter 22, “Maintaining Application State.”

In this section, you learn how to create an Anonymous Personalization Provider. To create this custom personalization provider, you need to modify three of the standard classes used by the Web Part Framework.

First, you need to create the Anonymous Personalization Provider class itself. This class is contained in Listing 29.13.

Example 29.13. AnonSqlPersonalizationProvider.vb

Imports System
Imports System.Web
Imports System.Web.UI.WebControls.WebParts

Namespace myControls

    ''' <summary>
    ''' Custom Personalizaton Provider which enables
    ''' anonymous personalization
    ''' </summary>
    Public Class AnonSqlPersonalizationProvider
        Inherits SqlPersonalizationProvider
        ''' <summary>
        ''' Saves personalization data to the database
        ''' </summary>
        Protected Overrides Sub SavePersonalizationBlob(ByVal webPartManager As  WebPartManager, ByVal path As String, ByVal userName As String, ByVal dataBlob() As Byte)
            If Not HttpContext.Current.Request.IsAuthenticated Then
                userName = HttpContext.Current.Request.AnonymousID
            End If
            MyBase.SavePersonalizationBlob(webPartManager, path, userName, dataBlob)
        End Sub

        ''' <summary>
        ''' Loads personalization data from the database
        ''' </summary>
        Protected Overrides Sub LoadPersonalizationBlobs(ByVal webPartManager As  WebPartManager, ByVal path As String, ByVal userName As String, ByRef sharedDataBlob() As Byte, ByRef userDataBlob() As Byte)
            If Not HttpContext.Current.Request.IsAuthenticated Then
                userName = HttpContext.Current.Request.AnonymousID
            End If
            MyBase.LoadPersonalizationBlobs(webPartManager, path, userName, sharedDataBlob, userDataBlob)
        End Sub

        ''' <summary>
        ''' Deletes personalization data from the database
        ''' </summary>
        Protected Overrides Sub ResetPersonalizationBlob(ByVal webPartManager As  WebPartManager, ByVal path As String, ByVal userName As String)
            If Not HttpContext.Current.Request.IsAuthenticated Then
                userName = HttpContext.Current.Request.AnonymousID
            End If
            MyBase.ResetPersonalizationBlob(webPartManager, path, userName)
        End Sub

        ''' <summary>
        ''' Determines whether the page opens in User or Shared
        ''' personalization scope
        ''' </summary>
        Public Overrides Function DetermineInitialScope(ByVal webPartManager As WebPartManager, ByVal loadedState As PersonalizationState) As PersonalizationScope
            Return webPartManager.Personalization.InitialScope
        End Function

    End Class
End Namespace

The AnonPersonalizationProvider overrides four methods of the base SqlPersonalizationProvider class. If a user is anonymous, then the LoadPersonalizationBlob(), SavePersonalizationBlob(), and ResetPersonalizationBlob() methods use the anonymous ID associated with the user rather than the normal username. The DetermineInitialScope() method is also overridden because the default implementation of this method automatically puts anonymous users into Shared personalization scope.

Next, you need to modify the standard WebPartPersonalization class. The standard version of this class prevents anonymous users from modifying state information. The updated AnonWebPartPersonalization class is contained in Listing 29.14.

Example 29.14. AnonWebPartPersonalization.vb

Imports System
Imports System.Collections
Imports System.Web
Imports System.Web.UI.WebControls.WebParts

Namespace myControls

    ''' <summary>
    ''' Overrides the standard WebPartPersonalization class
    ''' to enable anonymous users to modify state.
    ''' </summary>
    Public Class AnonWebPartPersonalization
        Inherits WebPartPersonalization

        Public Sub New(ByVal webPartManager As WebPartManager)
            MyBase.New(webPartManager)
        End Sub

        Protected Overrides ReadOnly Property UserCapabilities() As IDictionary
            Get
                If HttpContext.Current.Request.IsAuthenticated = True Then
                 Return MyBase.UserCapabilities
                Else
                 Dim capabilities As Hashtable = New Hashtable()
                 capabilities.Add(WebPartPersonalization.ModifyStateUserCapability, WebPartPersonalization.ModifyStateUserCapability)
                    Return capabilities
                End If
            End Get
        End Property
    End Class
End Namespace

In Listing 29.14, the UserCapabilities property is overridden. The new version of this property ensures that all users, even anonymous users, have the capability to modify state information.

Next, because a custom WebPartPersonalization class has been created, the standard WebPartManager control has to be modified before you can use it. The updated WebPartManager control is contained in Listing 29.15.

Example 29.15. AnonWebPartManager.vb

Imports System
Imports System.Web.UI.WebControls.WebParts

Namespace myControls
    ''' <summary>
    ''' Modifies the base WebPartManager control
    ''' to use the AnonWebPartPersonalization class
    ''' </summary>
    Public Class AnonWebPartManager
        Inherits WebPartManager
        Protected Overrides Function CreatePersonalization() As WebPartPersonalization
            Return New AnonWebPartPersonalization(Me)
        End Function
    End Class
End Namespace

In Listing 29.15, the base CreatePersonalization() method of the WebPartManager control is overridden to use the custom AnonWebPartPersonalization class.

Next, you are ready to enable the Anonymous Personalization Provider. The web configuration file in Listing 29.16 contains the necessary configuration settings.

Warning

You might need to restart your application to load the new personalization provider.

Example 29.16. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
        <system.web>
                <anonymousIdentification enabled="True"/>
                <authentication mode="None" />
                <webParts>
                        <personalization defaultProvider="AnonProvider">
                                <providers>
                                        <add
            name="AnonProvider"
            type="myControls.AnonSqlPersonalizationProvider"
            connectionStringName="localSQLServer"/>
                                </providers>
                                <authorization>
          <allow users="Administrators" verbs="enterSharedScope"/>
                                </authorization>
                        </personalization>
                </webParts>
                </system.web>
</configuration>

CD Note

The configuration file in Listing 29.16 is saved with the name Web.Config_listing16 on the CD so that it does not interfere with the other code samples in this chapter.

The Web configuration file in Listing 29.16 does three things. First, it enables Anonymous Identification. When this feature is enabled, a GUID is generated automatically for each user and stored in a browser cookie. Second, the configuration file disables authentication by setting the Authentication Mode to the value None. Finally, the configuration file configures the AnonPersonalizationProvider as the default personalization provider for the application.

Warning

You might need to restart your application to load the new personalization provider.

At this point, it’s finally time to try out the Anonymous Personalization Provider. The Web Form page in Listing 29.17 can be customized by strangers.

Example 29.17. TestAnonymous.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="custom" Namespace="myControls" %>
<%@ Register TagPrefix="user" TagName="PersonalizationManager"
   Src="~/PersonalizationManager.ascx" %>
<%@ Register TagPrefix="user" TagName="TextPart" Src="~/TextPart.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<script runat="server">

    Protected Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs)
        WebPartManager1.DisplayMode = WebPartManager1.DisplayModes(e.Item.Text)
    End Sub

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .personalizationManager
        {
            border:dotted 2px orange;
            padding:5px;
            background-color:White;
            font:12px Arial, Sans-Serif;
        }
        .personalizationManager span
        {
            padding-right:10px;
            margin-right:10px;
            border-right:solid 1px black;
        }
        .personalizationManager a
        {
            text-decoration:none;
        }
        .column
        {
            float:left;
            width:30%;
            height:200px;
            margin-right:10px;
            border:solid 1px black;
            background-color: white;
        }
        .menu
        {
            margin:5px 0px;
        }
        html
        {
            background-color:#eeeeee;
        }
    </style>
    <title>Anonymous Personalization</title>
</head>
<body>
    <form id="form1" runat="server">
    <custom:AnonWebPartManager
        id="WebPartManager1"
        Runat="server" />

    <user:PersonalizationManager
        id="PersonalizationManager1"
        runat="server" />

        <asp:Menu
            id="Menu1"
            OnMenuItemClick="Menu1_MenuItemClick"
            Orientation="Horizontal"
            CssClass="menu"
            Runat="server">
            <Items>
            <asp:MenuItem Text="Browse" />
            <asp:MenuItem Text="Design" />
            </Items>
        </asp:Menu>

        <asp:WebPartZone
            id="WebPartZone1"
            CssClass="column"
            Runat="server">
            <ZoneTemplate>
            <user:TextPart
                id="TextPart1"
                Title="Text Part"
                Description="Displays block of text"
                Runat="server" />
            </ZoneTemplate>
        </asp:WebPartZone>

        <asp:WebPartZone
            id="WebPartZone2"
            CssClass="column"
            Runat="server" />
    </form>
</body>
</html>

After you open the page in Listing 29.17, you can update the text displayed by the TextPart Web Part (see Figure 29.8). If you close your browser and return to the website in two years, the same text will appear.

Using the Anonymous Personalization Provider.

Figure 29.8. Using the Anonymous Personalization Provider.

If you want to simulate two anonymous users, then you need to open two different types of browsers (opening multiple instances of Internet Explorer doesn’t work because they all share the same browser cookies). For example, you can open the Default.aspx page in both Internet Explorer and Mozilla Firefox and make different changes to the same page.

Warning

There is one undesirable consequence of the way that the Anonymous Personalization Provider was implemented in this chapter. Anonymous users are added to the aspnet_Users database table without being marked as anonymous. To fix this problem you need to modify the following line in the aspnet_PersonalizationPerUser_SetPageSettings stored procedure:

EXEC dbo.aspnet_Users_CreateUser @ApplicationId, @UserName, 0, @CurrentTimeUtc, @UserId OUTPUT

The 0 parameter hard codes all users as authenticated.

Summary

This chapter leapt into the details of Web Part personalization. First, you learned how to enable a user to make both User and Shared scoped personalization changes to a page. Next, you learned how to mark different properties of a Web Part as personalizable. You learned how to use the IPersonalizable interface for advanced personalization scenarios.

You also learned how to administer a Web Part application. In particular, you learned methods of pruning stale personalization data from a Web Part application. You also learned to generate a report of how much personalization data is being stored for each application user.

Finally, you studied the advanced topic of creating custom personalization providers. You learned how to create both a custom Query String Personalization Provider and a custom Anonymous Personalization Provider.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset