Team LiB
Previous Section Next Section

Defining Advanced Personalization Properties

As shown in the previous examples, all properties defined for a profile are saved as a string by default. This isn't mandatory, however, because you can place almost every object in the profile as long as it's serializable in some way.

Storing Base Types

Apart from using strings, you'll frequently wish to save other standard data types. You may, for example, store the date of birth of a user as DateTime. To do so, you only have to specify the class name of the desired data type via the attribute type when defining the profile properties.

In the following example, the user's birth date and a numeric value is saved. The value shows the number of years of experience the user has working with ASP.NET.

<personalization>
    <profile>
        <property name="Nickname" />
        <property name="Birthday" type="System.DateTime" />
        <property name="YearsExperienceASPNET" type="int" defaultValue="1" />
    </profile>
</personalization>

If necessary, specify the complete class name of the data type including the corresponding namespace, such as System.DateTime in this case. If you want to use a static standard value, you can add it with the attribute defaultValue. Otherwise, the implemented standard value will be used in case of value types.

The enlarged profile can be edited by each user through a new page named editprofile.aspx. As demonstrated in Listing 7-2, access to the properties of the profile object takes place in a type-safe way according to the data type assigned in the configuration previously shown (see Figure 7-4).

Click To expand
Figure 7-4: You can use any base data type for storing data in the user's profile.
Listing 7-2: Accessing the Profile Object in a Type-Safe Manner
Start example
<%@ page language="C#" master="~/MasterPage.master" %>

<script runat="server" language="c#">

void Page_Load(object sender, System.EventArgs e)
{
    if (this.IsPostBack == false)
    {
        this.LB_Username.Text = this.User.Identity.Name;
        this.TB_Nickname.Text = this.Profile.Nickname;
        this.TB_Birthday.Text = this.Profile.Birthday.ToShortDateString();
        this.TB_Experience.Text = this.Profile.YearsExperienceASPNET.ToString();
    }
}

void Button1_Click(object sender, System.EventArgs e)
{
    if (this.IsValid)
    {
        this.Profile.Nickname = this.TB_Nickname.Text;
        DateTime birthday;
        if (DateTime.TryParse(this.TB_Birthday.Text, out birthday))
        {
            this.Profile.Birthday = birthday;
        }

        int experience;
        if (int.TryParse(this.TB_Experience.Text, out experience))
        {
            this.Profile.YearsExperienceASPNET = experience;
        }
    }
}

</script>

<asp:content id="Content1"
    contentplaceholderid="ContentPlaceHolder1"
    runat="server">

    <table cellspacing="1" cellpadding="1" border="1">
        <tr>
            <td>User Name:</td>
            <td>
                <asp:label id="LB_Username" runat="server" />
            </td>
            <td>&nbsp;</td>
        </tr>
        <tr>
            <td>Nickname:</td>
            <td>
                <asp:textbox id="TB_Nickname" runat="server">
                </asp:textbox>
            </td>
            <td>&nbsp;</td>
        </tr>

        ...

    </table>
</asp:content>

End example

Storing Complex Types

Apart from standard data types, you can store other classes from the base class library within the profile as long as they are marked as serializable (using the Serializable attribute). To define the type, you therefore have to specify the class's full name, including its namespace.

All objects are serialized as a string by default. This is quite a new concept, and an example is shown in the following snippet. As you can see, the property values are stored right by each other. The values are distinguished by their start position and length.

PropertyNames: UserName:S:0:7:Theme:S:7:9:
PropertyValuesString: PatrickBasicBlue

With reference types, however, string serialization isn't advisable. Of course, this doesn't apply for the string type itself, which is basically a reference type, too. You should choose serialization in the form of XML or binary instead. You can assign the desired setting (String, Binary, or Xml) to the serializeAs attribute.

The following example shows the allocation of a StringCollection that is serialized in the database as XML. The StringCollection is available in the profile through the property Bookmarks.

<personalization>
    <profile>
        <property name="Nickname" />
        <property name="Birthday" type="System.DateTime" />
        <property name="YearsExperienceASPNET" type="int" defaultValue="1"/>
        <property name="Bookmarks"
            type="System.Collections.Specialized.StringCollection"
            serializeAs="Xml"/>
    </profile>
</personalization>

The new Profile property of the Page and HttpContext classes is used to store personal bookmarks (aka favorites). Taking advantage of this property, the Master Page has a new LinkButton control through which the URL of the current page can be dropped into the collection. The corresponding source code looks like this:

void LinkButton1_Click(object sender, System.EventArgs e)
{
    HttpPersonalization profile = (HttpPersonalization) this.Context.Profile;
    profile.Bookmarks.Add(this.Request.Url.ToString());
}

The conversion shown in the example is necessary because the code is placed in the Master Page, which doesn't allow type-safe access to the custom profile. The HttpContext class is used instead, which redelivers the profile across the HttpPersonalizationBaseClass base class and therefore won't give access to the custom properties. After the conversion, they are at your disposal as usual.

Note 

Please be aware that all reference types are already instantiated by the personalization system using the class's default constructor. So you can directly access the object without any further checks.

I've created a new page in the protected area to illustrate the workings of the previously shown source code. This page lists stored bookmarks and allows you to delete individual entries. The following example contains the source code of the page, and Figure 7-5 shows the action:

<%@ page language="C#" master="~/MasterPage.master" %>

<script runat="server" language="c#">

void Page_Load(object sender, System.EventArgs e)
{
    if (this.IsPostBack == false)
    {
        this.UpdateBookmarkList();
    }
}

void BT_DeleteBookmark_Click(object sender, System.EventArgs e)
{
    if (this.LB_Bookmarks.SelectedIndex != -1)
    {
        this.Profile.Bookmarks.RemoveAt(this.LB_Bookmarks.SelectedIndex);
        this.UpdateBookmarkList();
    }
}

private void UpdateBookmarkList()
{
    this.LB_Bookmarks.DataSource = this.Profile.Bookmarks;
    this.LB_Bookmarks.DataBind();
}

void BT_OpenBookmark_Click(object sender, System.EventArgs e)
{
    if (this.LB_Bookmarks.SelectedIndex != -1)
    {
        this.Response.Redirect(this.LB_Bookmarks.SelectedValue, true);
    }
}
</script>

<asp:content id="Content1"
    contentplaceholderid="ContentPlaceHolder1"
    runat="server">
    <asp:listbox id="LB_Bookmarks" runat="server"/>
    <br />
    <asp:button
        id="BT_OpenBookmark" runat="server"
        text="Open" onclick="BT_OpenBookmark_Click" />

    <asp:button id="BT_DeleteBookmark"
        runat="server" text="Delete"
        onclick="BT_DeleteBookmark_Click" />
</asp:content>
Click To expand
Figure 7-5: The bookmarks are stored using the StringCollection class.

Storing Custom Types

If you thought storing custom types must be doable in ASP.NET 2.0, you are right, of course! Yes, you can store any individual data in a profile. But again, this will work only under the condition that your class supports serialization.

A typical example for such a class is the shopping basket in a web application. I already gave you a glance at such an example early in this chapter. Wouldn't it be great if your customers could keep their shopping basket in between several visits until they finally place an order? I think so! And with the Profile Management system of ASP.NET 2.0, this can easily be done, as I'll demonstrate to you!

Creating a Simple Shopping Basket

The two classes Basket and BasketItem form the basis of your shopping basket. They represent the shopping basket itself as well as the single products inside of it. Basket is nothing but a derivation of the generic System.Collections.Generic.List class that has been slightly enhanced. Likewise, the second class, called BasketItem, is a simple business object prototype with four easy properties. Both classes are explicitly marked with attribute Serializable.

// Basket.cs
using System;

[Serializable]
public class Basket : System.Collections.Generic.List<BasketItem>
{
    public void Remove(int productId)
    {
        BasketItem item = this.FindItemByProductId(productId);
        if (item != null)
        {
            base.Remove(item);
        }
    }

    public BasketItem FindItemByProductId(int productId)
    {
        foreach (BasketItem item in this)
        {
            if (item.ProductId.Equals(productId))
            {
                   return item;
                }
            }
            return null;
        }
    }

    // BasketItem.cs
    using System;

[Serializable]
public class BasketItem
{
    private int productId;
    private string name;
    private int count;
    private decimal unitPrice;

    public BasketItem() {}

    public BasketItem(int productId, string name, int count, decimal unitPrice)
    {
        this.productId = productId;
        this.name = name;
        this.count = count;
        this.unitPrice = unitPrice;
    }

    public int ProductId
    {
        get { return this.productId; }
        set { this.productId = value; }
    }

    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }

    public int Count
    {
        get { return this.count; }
       set { this.count = value; }
   }

   public decimal UnitPrice
   {
      get { return this.unitPrice; }
      set { this.unitPrice = value; }
   }
}

The classes created and stored in the Code directory of the web site can now be included in the profile through the web.config configuration file:

<personalization>
    <profile>
        <property name="Basket" type="Basket" serializeAs="Xml" />
    </profile>
</personalization>

Done! From now on, you can save new items in the profile of the user, as this example demonstrates:

this.Profile.Basket.Add(new BasketItem(1, "Hello world product", 5, 12.34m));

The persisted shopping basket will still be available for users on their next visit.

Using the ObjectDataSource Control to Access the Basket

I've created a small page as an example to show you the flexible use of the profile object in conjunction with the new Data Controls. These are very helpful tools when it comes to the modification of the shopping basket. As with some of the Data Control examples I showed you in Chapter 3, you'll find a GridView control on this page that allows users to show, edit, and delete the current content of the shopping basket as well as a DetailsView control to add new items. The complete administration of the data is managed with an ObjectDataSource control.

<%@ page language="C#" master="~/MasterPage.master" %>

<asp:content id="Content1"
    contentplaceholderid="ContentPlaceHolder1"
    runat="server">
<asp:gridview id="GridView1" runat="server"
    datasourceid="ObjectDataSource1"
    autogeneratecolumns="False"
    datakeynames="ProductId">

    <columnfields>
        <asp:boundfield datafield="ProductId" readonly="True"
            headertext="Product ID">
        </asp:boundfield>
        <asp:boundfield datafield="Name" headertext="Name">
        </asp:boundfield>
        <asp:boundfield datafield="Count" headertext="Count">
        </asp:boundfield>
        <asp:boundfield datafield="UnitPrice" headertext="Unit Price">
        </asp:boundfield>
        <asp:commandfield showdeletebutton="True" showeditbutton="True">
        </asp:commandfield>
    </columnfields>

</asp:gridview>

<asp:detailsview id="DetailsView1"
    runat="server"
    datasourceid="ObjectDataSource1"
    autogeneraterows="False"
    defaultmode="Insert"
    datakeynames="ProductId">

    <rowfields>
        <asp:boundfield datafield="ProductId" readonly="True"
            headertext="Product ID">
        </asp:boundfield>
        <asp:boundfield datafield="Name" headertext="Name">
        </asp:boundfield>
        <asp:boundfield datafield="Count" headertext="Count">
        </asp:boundfield>
        <asp:boundfield datafield="UnitPrice" headertext="Unit Price">
        </asp:boundfield>
        <asp:commandfield showinsertbutton="True" showcancelbutton="False">
        </asp:commandfield>
    </rowfields>
</asp:detailsview>
    <asp:objectdatasource
        id="ObjectDataSource1"
        runat="server"
        selectmethod="GetBasket"
        typename="BasketManager"
        updatemethod="Update"
        insertmethod="Insert"
        deletemethod="Delete">

        <updateparameters>
            <asp:parameter name="UnitPrice" type="Decimal">
            </asp:parameter>
            <asp:parameter name="Count" type="Int32">
            </asp:parameter>
            <asp:parameter name="ProductId" type="Int32">
            </asp:parameter>
        </updateparameters>

        <insertparameters>
            <asp:parameter name="ProductId" type="Int32">
            </asp:parameter>
            <asp:parameter name="Name" type="String">
            </asp:parameter>
            <asp:parameter name="Count" type="Int32">
            </asp:parameter>
            <asp:parameter name="UnitPrice" type="Decimal">
            </asp:parameter>
        </insertparameters>
    </asp:objectdatasource>

</asp:content>

The communication between profile, shopping basket, and ObjectDataSource is handled by the BasketManager class, which offers the essential methods to select, edit, add, and delete entries. The source code of the class is shown in the following example:

using System;
using System.Web;
using System.Web.Personalization;

public class BasketManager
{
    public Basket GetBasket()
    {
        HttpPersonalizationBaseClass profile = HttpContext.Current.Profile;
        return (profile.GetPropertyValue("Basket") as Basket);
    }

    public void Delete(int ProductId)
    {
        Basket basket = this.GetBasket();
        basket.Remove(ProductId);
    }

    public void Update(int ProductId, string Name, int Count, decimal UnitPrice)
    {
        Basket basket = this.GetBasket();
        BasketItem item = basket.FindItemByProductId(ProductId);

        if (item != null)
        {
           item.Name = Name;
           item.Count = Count;
           item.UnitPrice = UnitPrice;
        }
    }

    public void Insert(int ProductId, string Name, int Count, decimal UnitPrice)
    {
        Basket basket = this.GetBasket();
        basket.Add(new BasketItem(ProductId, Name, Count, UnitPrice));
    }
}

As you can see from the preceding example and Figure 7-6, type-safe access to the profile isn't possible at present from within a separate code class. Therefore, the shopping basket has to be retrieved untyped using the GetPropertyValue() method implemented by the base class HttpPersonalizationBaseClass. Starting with the Beta version, it will presumably be possible to integrate a special namespace, ASP, by which you get access to the individual typed profile.

Click To expand
Figure 7-6: Any changes are directly reflected in the profile and available throughout the whole profile lifetime.

Team LiB
Previous Section Next Section