您的位置:首页 > 编程语言 > ASP

Storing User Information with ASP.NET 2.0 Profiles

2005-02-24 13:41 495 查看
Stephen Walther

Superexpert

August 2004

Applies to:

   Microsoft ASP.NET 2.0

   Microsoft Visual Studio 2005

Summary: Many ASP.NET applications need to track user preferences across multiple visits. In ASP.NET 1.x, adding this functionality was a manual process. With the new Profile object in ASP.NET 2.0, this becomes a simple process. Stephen Walther examines this object, and shows you how to use it to track user preferences, create a shopping cart, and more. (44 printed pages)

Contents

Introduction

User Profiles Overview

Defining User Profiles

Using Profile Groups

Using Complex Profile Properties

Inheriting a Profile

Migrating Anonymous Profile Settings

Configuring Profile Providers

Managing Profiles and Generating Profile Reports

Conclusion

Related Books

Introduction

Microsoft ASP.NET 2.0 supports a new object called the Profile object. This new object enables you to automatically store user information across multiple visits to a Web application. You can store any type of information within a user profile including both simple data types such as strings and integers and complex types such as custom objects. For example, you can use profiles to store user last names, shopping carts, user preferences, or Web site usage statistics.

In this article, you'll learn how to define user profiles for an application. We'll also examine how you can configure profiles to work with different providers. Finally, you'll learn how to manage and generate reports on user profiles.

User Profiles Overview

The Profile object is similar to the Session object, but better. Like the Session object, the Profile object is scoped to a particular user. In other words, each user of a Web application automatically has their own profile.

However, unlike the Session object, the Profile object is persistent. When you add an item to the Session object, the item disappears after you leave the Web site. When you modify the state of the Profile object, in contrast, the modifications are saved between visits to the Web site.

The Profile object uses the provider model to store information. By default, the contents of a user profile are automatically saved to a Microsoft Access database located in the Data folder of your Web application. However, as we'll see later in this article, you can store profile information by using other data providers such as Microsoft SQL Server or even an Oracle database.

Furthermore, unlike the Session object, the Profile object is strongly typed. The Session object is simply a collection of items. In contrast, the Profile object exposes strongly typed properties.

Using strongly typed properties has several advantages. For example, you get full Microsoft IntelliSense when using the Profile object in Microsoft Visual Web Developer. When you type Profile followed by a dot, IntelliSense pops up with a list of all of the properties you have defined for the profile.

Defining User Profiles

You define a user profile within either the Machine.Config file or the application root Web.Config file. You cannot create a Web.Config file containing a profile section in an application subfolder. This means that you cannot define more than one profile for a single application.

The Web.Config file in Listing 1 contains a simple profile definition. This profile defines three properties named FirstName, LastName, and PageVisits.

Listing 1. Web.Config

<configuration>
<system.web>
<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile>
<properties>
<add
name="FirstName"
defaultValue="??"
allowAnonymous="true" />
<add
name="LastName"
defaultValue="??"
allowAnonymous="true" />
<add
name="PageVisits"
type="Int32"
allowAnonymous="true"/>
</properties>
</profile>
</system.web>
</configuration>

This profile will be used with both anonymous and authenticated users. Therefore, the Web.Config file in Listing 1 includes an <anonymousIdentification> element, that automatically generates a unique ID for anonymous users. Notice also that all of the profile properties include an allowAnonymous attribute. This attribute is required when the profile properties are used with anonymous users.

The default data type for profile properties is the System.String data type. Since a type attribute is not provided for the FirstName and LastName profile properties, these properties are assumed to be String properties. The PageVisits property is assigned the type Int32 since it is used to represent an integer value.

Finally, notice that both the FirstName and LastName properties have defaultValue attributes. You can use the defaultValue attribute when working with simple data types such as strings and integers. You cannot provide default values for complex types.

After you define a profile, the next time a page is requested, a class that corresponds to the profile definition is automatically generated within your application. The automatically generated class is stored in the Temporary ASP.NET Files Directory (the same place the class files are stored for dynamically generated ASP.NET pages). The class is exposed by the Profile property of the HttpContext object.

After you define a profile in the Web.Config file, you can assign values to profile properties within any ASP.NET page like this:

[Visual Basic .NET]

Profile.FirstName = "Bill"

[C#]

Profile.FirstName = "Bill";

Any profile properties defined in the Web.Config file are exposed as properties of the Profile object.

The page in Listing 2 demonstrates how you can use a profile to persist user information. This page displays the values of the FirstName, LastName, and PageVisits properties. It also includes a form for modifying the values of the FirstName and LastName properties (see Figure 1). Finally, the Page_Load method updates the PageVisits profile property every time the page is requested.



Figure 1. Using a simple profile

Listing 2. Simple.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>
<script runat="server">

Sub Page_Load()
Profile.PageVisits += 1
End Sub

Sub UpdateProfile(ByVal s As Object, ByVal e As EventArgs)
Profile.FirstName = txtFirstName.Text
Profile.LastName = txtLastName.Text
End Sub

</script>

<html>
<head>
<title>Simple</title>
</head>
<body>
<form id="form1" runat="server">
<b>Name:</b> <%= Profile.FirstName %> <%= Profile.LastName %>
<br />
<b>Page Visits:</b> <%= Profile.PageVisits %>

<hr />

<b>First Name:</b>
<asp:TextBox ID="txtFirstName" Runat="Server" />
<br />
<b>Last Name:</b>
<asp:TextBox ID="txtLastName" Runat="Server" />
<br />
<asp:Button
Text="Update Profile"
OnClick="UpdateProfile"
Runat="server" />

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

Listing 2. Simple.aspx (C#)

<%@ Page Language="C#" %>
<script runat="server">

void Page_Load() {
Profile.PageVisits ++;
}

void UpdateProfile(Object s, EventArgs e) {
Profile.FirstName = txtFirstName.Text;
Profile.LastName = txtLastName.Text;
}

</script>

<html>
<head>
<title>Simple</title>
</head>
<body>
<form id="form1" runat="server">
<b>Name:</b> <%= Profile.FirstName %> <%= Profile.LastName %>
<br />
<b>Page Visits:</b> <%= Profile.PageVisits %>

<hr />

<b>First Name:</b>
<asp:TextBox ID="txtFirstName" Runat="Server" />
<br />
<b>Last Name:</b>
<asp:TextBox ID="txtLastName" Runat="Server" />
<br />
<asp:Button ID="Button1"
Text="Update Profile"
OnClick="UpdateProfile"
Runat="server" />

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

If you request the page in Listing 2 multiple times, you'll notice that the value of PageVisits increments. If you shutdown your Web browser, and request the page one week in the future, then the PageVisits property will retain its previous value. Profile properties are automatically saved for each user.

Using Profile Groups

You can define no more than one profile for an application. If you need to work with several profile properties, you might find it easier to work with the properties if you organize them into groups.

For example, the Web.Config file in Listing 3 contains a profile with two groups: Address and Preferences.

Listing 3. Web.Config

<configuration>
<system.web>
<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile>
<properties>
<group name="Address">
<add
name="Street"
allowAnonymous="true" />
<add
name="City"
allowAnonymous="true" />
</group>
<group name="Preferences">
<add
name="ReceiveNewsletter"
type="Boolean"
defaultValue="false"
allowAnonymous="true" />
</group>
</properties>
</profile>
</system.web>
</configuration>

When you define a profile by using groups, you use the group names when setting and reading profile properties. For example, you would use the following syntax to assign values to the three profile properties defined by the profile in Listing 3:

[Visual Basic .NET]

Profile.Address.City = "Modesto"
Profile.Address.Street = "111 King Arthur Ln"
Profile.Preferences.ReceiveNewsletter = False

[C#]

Profile.Address.City = "Modesto";
Profile.Address.Street = "111 King Arthur Ln";
Profile.Preferences.ReceiveNewsletter = false;

A profile definition can only contain one level of groups. In other words, you cannot nest additional groups below a profile group.

Using Complex Profile Properties

To this point, we have declared profiles that contain properties with simple data types such as strings and integers. You also can declare complex types when creating profile properties.

For example, imagine that you want to store a shopping cart in a profile. If you store a shopping cart in the Profile object, then the shopping cart will be automatically available every time a user returns to your Web site.

Listing 4 contains a profile definition that has a property named ShoppingCart. Notice that the type attribute refers to a class named ShoppingCart (we'll create this class in a moment). The type attribute should refer to a valid class name.

Also notice that the definition includes a serializeAs attribute. This attribute is used to persist the ShoppingCart class using the binary serializer rather than the XML serializer.

Listing 4. Web.Config

<configuration>
<system.web>
<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile>
<properties>
<add
name="ShoppingCart"
type="ShoppingCart"
serializeAs="Binary"
allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>

Listing 5 contains the code for a simple shopping cart. This shopping cart has methods for adding and removing items from the shopping cart. It also contains a property that represents all of the items in the shopping cart and a property that represents the total price of all of the items in the shopping cart.

Listing 5. ShoppingCart (Visual Basic .NET)

Imports Microsoft.VisualBasic

<Serializable()> _
Public Class ShoppingCart
Public _CartItems As New Hashtable()

' Return all the items from the Shopping Cart
Public ReadOnly Property CartItems() As ICollection
Get
Return _CartItems.Values
End Get
End Property

' The sum total of the prices
Public ReadOnly Property Total() As Decimal
Get
Dim sum As Decimal
For Each item As CartItem In _CartItems.Values
sum += item.Price * item.Quantity
Next
Return sum
End Get
End Property

' Add a new item to the shopping cart
Public Sub AddItem(ByVal ID As Integer, _
ByVal Name As String, ByVal Price As Decimal)
Dim item As CartItem = CType(_CartItems(ID), CartItem)
If item Is Nothing Then
_CartItems.Add(ID, New CartItem(ID, Name, Price))
Else
item.Quantity += 1
_CartItems(ID) = item
End If
End Sub

' Remove an item from the shopping cart
Public Sub RemoveItem(ByVal ID As Integer)
Dim item As CartItem = CType(_CartItems(ID), CartItem)
If item Is Nothing Then
Return
End If
item.Quantity -= 1
If item.Quantity = 0 Then
_CartItems.Remove(ID)
Else
_CartItems(ID) = item
End If
End Sub

End Class

<Serializable()> _
Public Class CartItem

Private _ID As Integer
Private _Name As String
Private _Price As Decimal
Private _Quantity As Integer = 1

Public ReadOnly Property ID() As Integer
Get
Return _ID
End Get
End Property

Public ReadOnly Property Name() As String
Get
Return _Name
End Get
End Property

Public ReadOnly Property Price() As Decimal
Get
Return _Price
End Get
End Property

Public Property Quantity() As Integer
Get
Return _Quantity
End Get
Set(ByVal value As Integer)
_Quantity = value
End Set
End Property

Public Sub New(ByVal ID As Integer, _
ByVal Name As String, ByVal Price As Decimal)
_ID = ID
_Name = Name
_Price = Price
End Sub
End Class

Listing 5. ShoppingCart (C#)

using System;
using System.Collections;

[Serializable]
public class ShoppingCart
{
public Hashtable _CartItems = new Hashtable();

// Return all the items from the Shopping Cart
public ICollection CartItems
{
get { return _CartItems.Values; }
}

// The sum total of the prices
public decimal Total
{
get
{
decimal sum = 0;
foreach (CartItem item in _CartItems.Values)
sum += item.Price * item.Quantity;
return sum;
}
}

// Add a new item to the shopping cart
public void AddItem(int ID, string Name, decimal Price)
{
CartItem item = (CartItem)_CartItems[ID];
if (item == null)
_CartItems.Add(ID, new CartItem(ID, Name, Price));
else
{
item.Quantity++;
_CartItems[ID] = item;
}
}

// Remove an item from the shopping cart
public void RemoveItem(int ID)
{
CartItem item = (CartItem)_CartItems[ID];
if (item == null)
return;
item.Quantity--;
if (item.Quantity == 0)
_CartItems.Remove(ID);
else
_CartItems[ID] = item;
}

}

[Serializable]
public class CartItem
{
private int _ID;
private string _Name;
private decimal _Price;
private int _Quantity = 1;

public int ID
{
get { return _ID; }
}

public string Name
{
get { return _Name; }
}

public decimal Price
{
get { return _Price; }
}

public int Quantity
{
get { return _Quantity; }
set { _Quantity = value; }
}

public CartItem(int ID, string Name, decimal Price)
{
_ID = ID;
_Name = Name;
_Price = Price;
}
}

If you add the code in Listing 5 to the Code folder in your application, then the shopping cart will be automatically compiled.

There's one thing in particular that you should notice about the code in Listing 5. Notice that both the ShoppingCart and CartItem classes are marked as serializable. This is important since these objects must be serialized when they are stored by the Profile object.

Finally, the page in Listing 6 displays a list of products that you can add to the shopping cart (see Figure 2). Notice that the shopping cart is loaded from the Profile object in the BindShoppingCart method. This method binds a GridView control to the shopping cart items represented by the CartItems property of the ShoppingCart class.



Figure 2. Storing a shopping cart in a profile

The AddCartItem method adds a new product to the shopping cart. Notice that the AddCartItem method includes code that checks whether or not the ShoppingCart object exists in the Profile. You must instantiate any objects stored in the Profile object yourself. They are not instantiated automatically.

Finally, the RemoveCartItem method removes a product from the shopping cart. This method simply calls the RemoveItem method on the ShoppingCart object stored in the Profile object.

Listing 6 - Products.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>

<script runat="server">

Sub Page_Load()
If Not IsPostBack Then
BindShoppingCart()
End If
End Sub

Sub BindShoppingCart()
If Not Profile.ShoppingCart Is Nothing Then
CartGrid.DataSource = Profile.ShoppingCart.CartItems
CartGrid.DataBind()
lblTotal.Text = Profile.ShoppingCart.Total.ToString("c")
End If
End Sub

Sub AddCartItem(ByVal s As Object, ByVal e As EventArgs)
Dim row As GridViewRow = ProductGrid.SelectedRow

Dim ID As Integer = CInt(ProductGrid.SelectedDataKey.Value)
Dim Name As String = row.Cells(1).Text
Dim Price As Decimal = CDec(row.Cells(2).Text)

If Profile.ShoppingCart Is Nothing Then
Profile.ShoppingCart = New ShoppingCart
End If
Profile.ShoppingCart.AddItem(ID, Name, Price)
BindShoppingCart()
End Sub

Sub RemoveCartItem(ByVal s As Object, ByVal e As EventArgs)
Dim ID As Integer = CInt(CartGrid.SelectedDataKey.Value)
Profile.ShoppingCart.RemoveItem(ID)
BindShoppingCart()
End Sub
</script>

<html>
<head>
<title>Products</title>
</head>
<body>
<form id="form1" runat="server">

<table width="100%">
<tr>
<td valign="top">
<h2>Products</h2>
<asp:GridView
ID="ProductGrid"
DataSourceID="ProductSource"
DataKeyNames="ProductID"
AutoGenerateColumns="false"
OnSelectedIndexChanged="AddCartItem"
ShowHeader="false"
CellPadding="5"
Runat="Server">
<Columns>
<asp:ButtonField
CommandName="select"
Text="Buy" />
<asp:BoundField
DataField="ProductName" />
<asp:BoundField
DataField="UnitPrice"
DataFormatString="{0:c}" />
</Columns>
</asp:GridView>

<asp:SqlDataSource
ID="ProductSource"
ConnectionString=
"Server=localhost;Database=Northwind;Trusted_Connection=true;"
SelectCommand=
"SELECT ProductID,ProductName,UnitPrice FROM Products"
Runat="Server" />
</td>
<td valign="top">
<h2>Shopping Cart</h2>
<asp:GridView
ID="CartGrid"
AutoGenerateColumns="false"
DataKeyNames="ID"
OnSelectedIndexChanged="RemoveCartItem"
CellPadding="5"
Width="300"
Runat="Server">
<Columns>
<asp:ButtonField
CommandName="select"
Text="Remove" />
<asp:BoundField
DataField="Name"
HeaderText="Name" />
<asp:BoundField
DataField="Price"
HeaderText="Price"
DataFormatString="{0:c}" />
<asp:BoundField
DataField="Quantity"
HeaderText="Quantity" />
</Columns>
</asp:GridView>
<b>Total:</b>
<asp:Label ID="lblTotal" Runat="Server" />
</td>
</tr>
</table>
</form>
</body>
</html>

Listing 6. Products.aspx (C#)

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Globalization" %>
<script runat="server">

void Page_Load() {
if (!IsPostBack)
BindShoppingCart();
}

void BindShoppingCart()
{
if (Profile.ShoppingCart != null)
{
CartGrid.DataSource = Profile.ShoppingCart.CartItems;
CartGrid.DataBind();
lblTotal.Text = Profile.ShoppingCart.Total.ToString("c");
}
}

void AddCartItem(Object s, EventArgs e)
{
GridViewRow row = ProductGrid.SelectedRow;

int ID = (int)ProductGrid.SelectedDataKey.Value;
String Name = row.Cells[1].Text;
decimal Price = Decimal.Parse(row.Cells[2].Text,
NumberStyles.Currency);

if (Profile.ShoppingCart == null)
Profile.ShoppingCart = new ShoppingCart();

Profile.ShoppingCart.AddItem(ID, Name, Price);
BindShoppingCart();
}

void RemoveCartItem(Object s, EventArgs e)
{
int ID = (int)CartGrid.SelectedDataKey.Value;
Profile.ShoppingCart.RemoveItem(ID);
BindShoppingCart();
}
</script>

<html>
<head>
<title>Products</title>
</head>
<body>
<form id="form1" runat="server">

<table width="100%">
<tr>
<td valign="top">
<h2>Products</h2>
<asp:GridView
ID="ProductGrid"
DataSourceID="ProductSource"
DataKeyNames="ProductID"
AutoGenerateColumns="false"
OnSelectedIndexChanged="AddCartItem"
ShowHeader="false"
CellPadding="5"
Runat="Server">
<Columns>
<asp:ButtonField
CommandName="select"
Text="Buy" />
<asp:BoundField
DataField="ProductName" />
<asp:BoundField
DataField="UnitPrice"
DataFormatString="{0:c}" />
</Columns>
</asp:GridView>

<asp:SqlDataSource
ID="ProductSource"
ConnectionString=
"Server=localhost;Database=Northwind;Trusted_Connection=true;"
SelectCommand=
"SELECT ProductID,ProductName,UnitPrice FROM Products"
Runat="Server" />
</td>
<td valign="top">
<h2>Shopping Cart</h2>
<asp:GridView
ID="CartGrid"
AutoGenerateColumns="false"
DataKeyNames="ID"
OnSelectedIndexChanged="RemoveCartItem"
CellPadding="5"
Width="300"
Runat="Server">
<Columns>
<asp:ButtonField
CommandName="select"
Text="Remove" />
<asp:BoundField
DataField="Name"
HeaderText="Name" />
<asp:BoundField
DataField="Price"
HeaderText="Price"
DataFormatString="{0:c}" />
<asp:BoundField
DataField="Quantity"
HeaderText="Quantity" />
</Columns>
</asp:GridView>
<b>Total:</b>
<asp:Label ID="lblTotal" Runat="Server" />
</td>
</tr>
</table>
</form>
</body>
</html>

Inheriting a Profile

You also can define a profile by inheriting the profile from an existing class. This option is valuable when you need to use the same profile with multiple applications.

For example, the class in Listing 7 represents a number of user properties. Notice that the class derives from the HttpProfileBase class (found in the System.Web.Profile namespace).

Listing 7. UserInfo (Visual Basic .NET)

Imports Microsoft.VisualBasic
Imports System.Web.Profile

Public Class UserInfo
Inherits HttpProfileBase

Private _FirstName As String
Private _LastName As String

Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property

Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property

End Class

Listing 7. UserInfo (C#)

using System;
using System.Web.Profile;

public class UserInfo : HttpProfileBase
{
private string _FirstName;
private string _LastName;

public string FirstName
{
get { return _FirstName; }
set { _FirstName = value; }
}
public string LastName
{
get { return _LastName; }
set { _LastName = value; }
}
}

The Web.Config file in Listing 8 contains a profile that inherits from the UserInfo class. All of the properties exposed in the UserInfo class are exposed by the profile generated by this definition.

Listing 8. Web.Config

<configuration>
<system.web>
<authentication mode="Forms" />
<anonymousIdentification enabled="true" />
<profile inherits="UserInfo" />
</system.web>
</configuration>

Migrating Anonymous Profile Settings

You can use the Profile object with both anonymous and authenticated users. However, when the same user switches between an anonymous and authenticated state, the Profile object exhibits potentially confusing behavior.

When the Profile object is used with an anonymous user, the user profile is associated with a randomly generated identifier assigned to the user. This random identifier is stored in a browser cookie. Whenever the same user returns to the application, the profile settings are automatically reloaded for the user.

If the anonymous user is authenticated, any profile properties associated with the user are lost. A new profile is generated for the user. Profile information is now associated with the authenticated username and not the randomly generated identifier.

If the user signs out, becoming anonymous, the user profile does not revert to its previous anonymous user profile values. A new randomly generated identifier is created for the user. Once again, a new profile is generated.

The best way to understand how all of this works is to take a look at a sample page. The Web.Config file in Listing 9 defines a profile with a single property named FavoriteColor.

Listing 9. Web.Config

<configuration>
<system.web>

<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile>
<properties>
<add
name="FavoriteColor"
allowAnonymous="true"
defaultValue="Red" />
</properties>
</profile>
</system.web>
</configuration>

The page in Listing 10 contains both a login and logout button. It also contains a form for updating the FavoriteColor profile property.

Listing 10. Anonymous.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>

<script runat="server">

Sub Login(ByVal s As Object, ByVal e As EventArgs)
FormsAuthentication.SetAuthCookie("Bill", False)
Response.Redirect(Request.Path)
End Sub

Sub Logout(ByVal s As Object, ByVal e As EventArgs)
FormsAuthentication.SignOut()
Response.Redirect(Request.Path)
End Sub

Sub UpdateProfile(ByVal s As Object, ByVal e As EventArgs)
Profile.FavoriteColor = txtFavoriteColor.Text
End Sub

Sub Page_PreRender()
lblUsername.Text = Profile.UserName
lblFavoriteColor.Text = Profile.FavoriteColor
End Sub

</script>

<html>
<head>
<title>Anonymous</title>
</head>
<body>
<form id="form1" runat="server">

<asp:Button ID="Button1"
Text="Login"
OnClick="Login"
Runat="Server" />
<asp:Button ID="Button2"
Text="Logout"
OnClick="Logout"
Runat="Server" />
<hr />
<asp:TextBox
id="txtFavoriteColor"
Runat="Server" />
<asp:Button ID="Button3"
Text="Update Profile"
OnClick="UpdateProfile"
Runat="Server" />
<hr />
<b>Username:</b>
<asp:Label
id="lblUsername"
Runat="Server" />
<br />
<b>Favorite Color:</b>
<asp:Label
id="lblFavoriteColor"
Runat="Server" />

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

Listing 10. Anonymous.aspx (C#)

<%@ Page Language="C#" %>

<script runat="server">

void Login(Object s, EventArgs e)
{
FormsAuthentication.SetAuthCookie("Bill", false);
Response.Redirect(Request.Path);
}

void Logout(Object s, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect(Request.Path);
}

void UpdateProfile(Object s, EventArgs e)
{
Profile.FavoriteColor = txtFavoriteColor.Text;
}

void Page_PreRender()
{
lblUsername.Text = Profile.UserName;
lblFavoriteColor.Text = Profile.FavoriteColor;
}

</script>

<html>
<head>
<title>Anonymous</title>
</head>
<body>
<form id="form1" runat="server">

<asp:Button
Text="Login"
OnClick="Login"
Runat="Server" />
<asp:Button ID="Button1"
Text="Logout"
OnClick="Logout"
Runat="Server" />
<hr />
<asp:TextBox
id="txtFavoriteColor"
Runat="Server" />
<asp:Button
Text="Update Profile"
OnClick="UpdateProfile"
Runat="Server" />
<hr />
<b>Username:</b>
<asp:Label
id="lblUsername"
Runat="Server" />
<br />
<b>Favorite Color:</b>
<asp:Label
id="lblFavoriteColor"
Runat="Server" />

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

When you first open the page in Listing 10, the value of the UserName profile property is a randomly generated identifier (see Figure 3). If you click the Login button, then you will be authenticated with the UserName Bill.



Figure 3. Using anonymous and authenticated profiles

The page in Listing 10 includes a form for updating the value of the FavoriteColor profile property. Notice that if you login, logout, and then login once again, a distinct profile is generated for each state. If you login and logout, a new randomly generated identifier is created.

There are many situations in which you'll want to carry over profile property values from an anonymous to an authenticated state. If you need to migrate profile property values, then you can take advantage of the MigrateAnonymous event of the ProfileModule class. This event must be handled in the Global.asax file. The Global.asax in Listing 11 illustrates how you can migrate the FavoriteColor profile property.

Listing 11. Global.asax (Visual Basic .NET)

<%@ Application Language="VB" %>
<script runat="server">

Sub Profile_MigrateAnonymous(ByVal s As Object, _
ByVal e As ProfileMigrateEventArgs)
Dim anonProfile As HttpProfile = _
Profile.GetProfile(e.AnonymousId)
Profile.FavoriteColor = anonProfile.FavoriteColor
End Sub

</script>

Listing 11. Global.asax (C#)

<%@ Application Language="C#" %>
<script runat="server">

void Profile_MigrateAnonymous(Object s,
ProfileMigrateEventArgs e)
{
HttpProfile anonProfile =
Profile.GetProfile(e.AnonymousId);
Profile.FavoriteColor = anonProfile.FavoriteColor;
}

</script>

The GetProfile() method of the Profile class is used to get the anonymous profile. This method accepts a profile identifier and returns the corresponding profile. The ProfileMigrateEventArgs object contains the anonymous identifier.

Configuring Profile Providers

By default, profiles are stored in a Microsoft Access database stored in the Data folder of your application. If you are building a high performance application then you'll want to store user profiles in a more scalable database such as Microsoft SQL Server.

Profiles use the provider model. The provider model enables you to specify where information is stored by modifying configuration settings within the Machine.Config or Web.Config file.

ASP.NET 2.0 ships with two profile providers: the AccessProfileProvider and the SqlProfileProvider. If you are feeling ambitious, you can even create your own provider by deriving a new class from the base ProfileProvider class. For example, you can create a custom profile provider that works with an Oracle database.

There are two steps required for using a Microsoft SQL Server database to store profile information. You must setup your SQL Server database and you must make a change to your configuration file.

The ASP.NET 2.0 framework includes a utility for configuring a SQL database to store profile information. This utility is named aspnet_regsql and it is located in your Windows\Microsoft.NET\Framework\[version] folder. When you execute this utility, you get the ASP.NET SQL Server Setup Wizard illustrated in Figure 4.



Figure 4. Using the ASP.NET SQL Server setup

The SQL Server Setup Wizard guides you through the steps necessary for adding the required tables and stored procedures to use the SqlProfileProvider. By default, the wizard creates a new database named aspnetdb.

After your SQL Server database is configured, you need to modify either your Web.Config or Machine.Config file to use the SqlProfileProvider. The configuration file in Listing 12 contains the necessary configuration settings.

Listing 12. Web.Config

<configuration>
<system.web>

<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile defaultProvider="AspNetSqlProvider">
<properties>
<add
name="FirstName"
allowAnonymous="true" />
<add
name="LastName"
allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>

In Listing 12, the profile declaration includes a defaultProvider attribute. This attribute points to a profile provider named the AspNetSqlProvider. This provider connects to the local SQL Server database named aspnetdb.

The AspNetSqlProvider is provided for you. If you want to connect to a database located on a different server or connect to a different database on the local server, then you need to configure a new provider. For example, the configuration file in Listing 13 connects to a database server named myDBServer.

Listing 13. Web.Config

<configuration >
<connectionStrings>
<add
name="myConnectionString"
connectionString=
"Server=myDBServer;Trusted_Connection=true;database=aspnetdb" />
</connectionStrings>

<system.web>

<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile defaultProvider="MyProfileProvider">
<providers>
<add
name="MyProfileProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="myConnectionString" />
</providers>
<properties>
<add
name="FirstName"
allowAnonymous="true" />
<add
name="LastName"
allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>

The <profile> section in the Web.Config file in Listing 13 contains a <provider> section. This section defines a new provider named MyProfileProvider. This provider uses a database connection string named myConnectionString. The connection string is defined in the <connectionStrings> section at the top of the configuration file.

Managing Profiles and Generating Profile Reports

The Profile object automatically saves profile information for users. This feature is both a blessing and curse. It is a blessing since you do not need to write all the logic to save the data yourself. It is a curse because potentially a huge amount of unused data could accumulate in your database.

Fortunately, the ASP.NET 2.0 framework includes a class that you can use to manage profiles: the ProfileManager class. This class has a number of utility methods that you can use to manage profiles and generate reports on profiles. Here is a list of the more important methods included in this class:

DeleteInactiveProfiles. Enables you to delete all profiles older than a specified date.

DeleteProfile. Enables you to delete a profile associated with a specified username.

DeleteProfiles. Enables you to delete a set of profiles.

FindInactiveProfilesByUserName. Returns a collection of ProfileInfo objects that represent profiles that have been inactive since a specified date and match a specified name.

FindProfilesByUserName. Returns a collection of ProfileInfo objects that represent profiles that match a specified username.

GetAllInactiveProfiles. Returns a collection of ProfileInfo objects that represent profiles that have been inactive since a specified date.

GetAllProfiles. Returns a collection of ProfileInfo objects that represent all profiles.

GetNumberOfInactiveProfiles. Returns an integer that represents the number of profiles that have been inactive since a specified date.

GetNumberOfProfiles. Returns an integer that represents the total number of profiles.

None of these methods return a complete profile, although many of the methods return a collection of ProfileInfo objects. The ProfileInfo object represents the following profile properties:

IsAnonymous. A Boolean value that represents whether or not the profile is associated with an anonymous or authenticated user.

LastActivityDate. A date and time that represents the last time the profile was accessed.

LastUpdatedDate. A date and time that represents the last time the profile was updated.

Size. An integer value that represents the size of the profile as stored by the profile provider.

UserName. A string that represents the user associated with the profile.

Several of the ProfileManager methods include additional parameters that support paging. For example, one overloaded version of the GetAllProfiles method includes parameters for the page index, page size, and total records. The paging parameters are useful when you want to page through profile information in a Web page.

The ProfileManager class can be used both within an ASP.NET page and outside of an ASP.NET page. For example, you might want to create a console application that automatically executes once a day and cleans up inactive profiles. The console application in Listing 14 deletes all profiles older than 7 days. You could schedule this console application by using Windows Scheduled Tasks.

Listing 14. DeleteInactiveProfiles (Visual Basic .NET)

Imports System.Web.Profile

Public Class DeleteInactiveProfiles

Public Shared Sub Main()
Dim deleted As Integer
deleted =
ProfileManager.DeleteInactiveProfiles(
ProfileAuthenticationOption.All,
DateTime.Now.AddDays(-7))
Console.WriteLine("Deleted " & deleted & " profiles" )
End Sub

End Class

Listing 14. DeleteInactiveProfiles (C#)

using System;
using System.Web.Profile;

public class DeleteInactiveProfiles
{
public static void Main()
{
int deleted = 0;
deleted =
ProfileManager.DeleteInactiveProfiles(
ProfileAuthenticationOption.All,
DateTime.Now.AddDays(-7));
Console.WriteLine("Deleted " +
deleted.ToString() + " profiles" );
}

}

You can compile the code in Listing 14 by executing the following command from the command line:

[Visual Basic .NET]

C:\WINDOWS\Microsoft.NET\Framework\v2.0.40607\vbc
/r:System.Web.dll DeleteInactiveProfiles.vb

[C#]

C:\WINDOWS\Microsoft.NET\Framework\v2.0.40607\csc
DeleteInactiveProfiles.cs

You can also use the ProfileManager class to generate reports on the information contained in profiles. For example, imagine that you want to create a user survey and store the survey results in the Profile object. Imagine, furthermore, that you want to create a page that generates a report on all of the survey results.

The Web.Config file in Listing 15 contains three properties: SurveyCompleted, FavoriteLanguage, and FavoriteEnvironment.

Listing 15. Web.Config

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<system.web>

<authentication mode="Forms" />

<anonymousIdentification enabled="true" />

<profile>
<properties>
<add
name="SurveyCompleted"
type="Boolean"
allowAnonymous="true" />
<add
name="FavoriteLanguage"
allowAnonymous="true" />
<add
name="FavoriteEnvironment"
allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>

The page in Listing 16 displays a simple user survey. The page consists of two Panel controls. The first panel contains the two survey questions. When the user completes the survey, the first panel is hidden and the second panel is displayed. The second Panel contains text that thanks the user for completing the survey.

Listing 16. Survey.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>

<script runat="server">

Sub SaveSurvey(ByVal s As Object, ByVal e As EventArgs)
Profile.FavoriteLanguage = rdlLanguage.SelectedItem.Text
Profile.FavoriteEnvironment = rdlEnvironment.SelectedItem.Text
Profile.SurveyCompleted = True
End Sub

Sub Page_PreRender()
If Profile.SurveyCompleted Then
pnlSurvey.Visible = False
pnlSurveyCompleted.Visible = True
Else
pnlSurvey.Visible = True
pnlSurveyCompleted.Visible = False
End If
End Sub

</script>

<html>
<head>
<title>Survey</title>
</head>
<body>
<form id="form1" runat="server">

<asp:Panel ID="pnlSurvey" Runat="Server">
What is your favorite programming language?
<br />
<asp:RadioButtonList
id="rdlLanguage"
runat="Server">
<asp:ListItem Text="VB.NET" Selected="True" />
<asp:ListItem Text="C#" />
<asp:ListItem Text="J#" />
</asp:RadioButtonList>
<p> </p>
What is your favorite development environment?
<br />
<asp:RadioButtonList
id="rdlEnvironment"
runat="Server">
<asp:ListItem Text="VS.NET" Selected="True" />
<asp:ListItem Text="Web Matrix" />
<asp:ListItem Text="Notepad" />
</asp:RadioButtonList>
<p> </p>
<asp:Button
Text="Submit Survey"
Onclick="SaveSurvey"
Runat="Server" />
</asp:Panel>
<asp:Panel ID="pnlSurveyCompleted" Runat="Server">
Thank you for completing the survey!
</asp:Panel>
</form>
</body>
</html>

Listing 16. Survey.aspx (C#)

<%@ Page Language="C#" %>

<script runat="server">

void SaveSurvey(Object s, EventArgs e)
{
Profile.FavoriteLanguage = rdlLanguage.SelectedItem.Text;
Profile.FavoriteEnvironment = rdlEnvironment.SelectedItem.Text;
Profile.SurveyCompleted = true;
}

void Page_PreRender()
{
if (Profile.SurveyCompleted)
{
pnlSurvey.Visible = false;
pnlSurveyCompleted.Visible = true;
}
else
{
pnlSurvey.Visible = true;
pnlSurveyCompleted.Visible = false;
}
}

</script>

<html>
<head>
<title>Survey</title>
</head>
<body>
<form id="form1" runat="server">

<asp:Panel ID="pnlSurvey" Runat="Server">
What is your favorite programming language?
<br />
<asp:RadioButtonList
id="rdlLanguage"
runat="Server">
<asp:ListItem Text="VB.NET" Selected="True" />
<asp:ListItem Text="C#" />
<asp:ListItem Text="J#" />
</asp:RadioButtonList>
<p> </p>
What is your favorite development environment?
<br />
<asp:RadioButtonList
id="rdlEnvironment"
runat="Server">
<asp:ListItem Text="VS.NET" Selected="True" />
<asp:ListItem Text="Web Matrix" />
<asp:ListItem Text="Notepad" />
</asp:RadioButtonList>
<p> </p>
<asp:Button ID="Button1"
Text="Submit Survey"
Onclick="SaveSurvey"
Runat="Server" />
</asp:Panel>
<asp:Panel ID="pnlSurveyCompleted" Runat="Server">
Thank you for completing the survey!
</asp:Panel>
</form>
</body>
</html>

The page in Listing 17 displays the survey results (see Figure 5). This page contains a GridView control that displays the contents of the ProfileInfo objects returned by the GetAllProfiles method of the ProfileManager class. When you click the Select link that appears next to each row in the GridView, you can see the actual responses to the survey questions. The GetProfile() method is called on the Profile class to return the actual profile that corresponds to a user identifier.




Figure 5. Displaying survey results

Listing 17. SurveyResults.aspx (Visual Basic .NET)

<%@ Page Language="VB" %>
<script runat="server">

Sub Page_Load()
ResultsGrid.DataSource = _
ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
ResultsGrid.DataBind()
End Sub

Sub DisplayProfileDetails(ByVal s As Object, ByVal e As EventArgs)
Dim SelectedProfile As HttpProfile
SelectedProfile = Profile.GetProfile(ResultsGrid.SelectedValue)
lblLanguage.Text = SelectedProfile.FavoriteLanguage
lblEnvironment.Text = SelectedProfile.FavoriteEnvironment
End Sub

</script>

<html>
<head>
<title>Survey Results</title>
</head>
<body>
<form id="form1" runat="server">
<h2>Survey Results</h2>
<asp:GridView
id="ResultsGrid"
DataKeyNames="UserName"
AutoGenerateSelectButton="true"
OnSelectedIndexChanged="DisplayProfileDetails"
SelectedRowStyle-BackColor="LightYellow"
Runat="Server" />
<p> </p>
<h2>Survey Details</h2>
<b>Favorite Language:</b>
<asp:Label
id="lblLanguage"
Runat="Server" />
<br />
<b>Favorite Environment:</b>
<asp:Label
id="lblEnvironment"
Runat="Server" />

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

Listing 17. SurveyResults.aspx (C#)

<%@ Page Language="C#" %>
<script runat="server">

void Page_Load()
{
ResultsGrid.DataSource =
ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All);
ResultsGrid.DataBind();
}

void DisplayProfileDetails(Object s, EventArgs e)
{
HttpProfile SelectedProfile =
Profile.GetProfile(ResultsGrid.SelectedValue.ToString());
lblLanguage.Text = SelectedProfile.FavoriteLanguage;
lblEnvironment.Text = SelectedProfile.FavoriteEnvironment;
}

</script>

<html>
<head>
<title>Survey Results</title>
</head>
<body>
<form id="form1" runat="server">
<h2>Survey Results</h2>
<asp:GridView
id="ResultsGrid"
DataKeyNames="UserName"
AutoGenerateSelectButton="true"
OnSelectedIndexChanged="DisplayProfileDetails"
SelectedRowStyle-BackColor="LightYellow"
Runat="Server" />
<p> </p>
<h2>Survey Details</h2>
<b>Favorite Language:</b>
<asp:Label
id="lblLanguage"
Runat="Server" />
<br />
<b>Favorite Environment:</b>
<asp:Label
id="lblEnvironment"
Runat="Server" />

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

Conclusion

When building Web applications, I still expend far too much time and energy performing brain-dead tasks. One of these tasks is writing the necessary code to store and retrieve user information from the database. The Profile object does not introduce any new functionality that wasn't available in the ASP.NET 1.0 Framework. However, this new feature shields us from the tedious chore of writing code to save and retrieve information from a database. In other words, the Profile object will free us to concentrate on the more interesting aspects of building a Web application.

Related Books

A First Look at ASP.NET V. 2.0

ASP.NET 2.0 Beta Preview

Introducing Microsoft ASP.NET 2.0

ASP.NET Unleashed

 

About the author

Stephen Walther wrote the best-selling book on ASP.NET, ASP.NET Unleashed. He was also the architect and lead developer of the ASP.NET Community Starter Kit, a sample ASP.NET application produced by Microsoft. He has provided ASP.NET training to companies across the United States, including NASA and Microsoft, through his company Superexpert (http://www.superexpert.com).



Top of Page
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐