3.3 使用用户配置文件
ASP.NET 2.0 Framework提供了一种可选的不同于cookie和Session状态的方式存储用户信息:Profile对象。Profile对象提供强类型、可持久化的Session状态表单。
可以在应用程序的根Web配置文件定义一组Profile属性来创建Profile。ASP.NET Framework在后台动态编译一个包含这些属性的类。
例如,代码清单3-19所示的Web配置文件定义了一个Profile包含了三个属性:firstName、lastName和numverOfVisits。
代码清单3-19 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile>
<properties>
<add name="firstName" />
<add name="lastName" />
<add name="numberOfVisits" type="Int32" defaultValue="0" />
</properties>
</profile>
</system.web>
</configuration>
当定义Profile属性时,可以使用下面的属性:
q name——用于指定属性名称;
q type——用户指定属性类型。类型可以是任意类型,包括定义在App_Code文件夹中的自定义组件(默认值是字符串类型);
q defaultValue——用于指定属性默认值;
q readOnly——用于创建只读属性(默认值为false);
q serializeAs——用于指定一个属性如何持久化为静态持久化数据。可能的值有Binary、ProviderSpecific、String和Xml(默认值为ProviderSpecific);
q allowAnonymous——用于允许匿名用户读写属性(默认值为false);
q provider——用于关联属性到特定的Profile提供程序;
q customProviderData——用于传递自定义数据到Profile提供程序。
在Web配置文件中定义Profile后,可以使用Profile对象修改Profile属性。例如,代码清单3-20所示的页面使用一个表单修改firstName和lastName属性的值。并且,页面在每次被请求时自动更新numberOfVisits属性(见图3-7)。
代码清单3-20 ShowProfile.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_PreRender()
{
lblFirstname.Text = Profile.firstName;
lblLastName.Text = Profile.lastName;
Profile.numberOfVisits++;
lblNumberOfVisits.Text = Profile.numberOfVisits.ToString();
}
protected void btnUpdate_Click(object sender, EventArgs e)
{
Profile.firstName = txtNewFirstName.Text;
Profile.lastName = txtNewLastName.Text;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show Profile</title>
</head>
<body>
<form id="form1" runat="server">
<div>
First Name:
<asp:Label
id="lblFirstname"
Runat="server" />
<br /><br />
Last Name:
<asp:Label
id="lblLastName"
Runat="server" />
<br /><br />
Number of Visits:
<asp:Label
id="lblNumberOfVisits"
Runat="server" />
<hr />
<asp:Label
id="lblNewFirstName"
Text="New First Name:"
AssociatedControlID="txtNewFirstName"
Runat="server" />
<asp:TextBox
id="txtNewFirstName"
Runat="server" />
<br /><br />
<asp:Label
id="lblNewLastName"
Text="New Last Name:"
AssociatedControlID="txtNewLastName"
Runat="server" />
<asp:TextBox
id="txtNewLastName"
Runat="server" />
<br /><br />
<asp:Button
id="btnUpdate"
Text="Update Profile"
OnClick="btnUpdate_Click"
Runat="server" />
</div>
</form>
</body>
</html>
图3-7 显示Profile信息
注意,Profile属性被公开为强类型属性。例如,numberOfVisits属性被公开为一个整型属性,因为我们将其定义为一个整型属性。
理解Profile属性是持久化的非常重要。如果为一个用户设置Profile属性,那么就算该用户过500年也不会再返回网站,属性也会保留其值。不像Session状态,当为Profile属性赋值时,用户离开网站,值也不会消失。
Profile对象使用提供程序模型。默认的Profile提供程序是SqlProfileProvider。默认情况下,该提供程序保存Profile数据到名为ASPNETDB.mdf的Microsoft SQL Server 2005 Express数据库,保存在应用程序的App_Code文件夹。如果数据库不存在,第一次使用Profile对象时它会被自动创建。
默认情况下,不能为匿名用户保存Profile信息。ASP.NET Framework使用用户的身份认证标识关联Profile信息。对任何标准的ASP.NET Framework支持的身份验证方式,都可以使用Profile对象,包括表单身份认证和Windows身份认证(默认情况下,Windows身份验证被启用)。
注解 本节的后续部分,你将了解到如何为匿名用户保存Profile信息。
3.3.1 创建用户配置文件组
如果需要定义许多Profile属性,则需要将属性组织成组,使得属性更易管理。例如,代码清单3-21所示的Web配置文件定义了两个名叫Preference和ContactInfo的组。
代码清单3-21 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile>
<properties>
<group name="Preferences">
<add name="BackColor" defaultValue="lightblue"/>
<add name="Font" defaultValue="Arial"/>
</group>
<group name="ContactInfo">
<add name="Email" defaultValue="Your Email"/>
<add name="Phone" defaultValue="Your Phone"/>
</group>
</properties>
</profile>
</system.web>
</configuration>
代码清单3-22所示的页面演示如何读写不同组的属性。
代码清单3-22 ShowProfileGroups.aspx
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Drawing" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
// Display Contact Info
lblEmail.Text = Profile.ContactInfo.Email;
lblPhone.Text = Profile.ContactInfo.Phone;
// Apply Preferences
Style pageStyle = new Style();
pageStyle.BackColor = ColorTranslator.FromHtml(Profile.Preferences.BackColor);
pageStyle.Font.Name = Profile.Preferences.Font;
Header.StyleSheet.CreateStyleRule(pageStyle, null, "html");
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Email:
<asp:Label
id="lblEmail"
Runat="server" />
<br /><br />
Phone:
<asp:Label
id="lblPhone"
Runat="server" />
</div>
</form>
</body>
</html>
3.3.2 支持匿名用户
默认情况下,匿名用户不能修改Profile属性。问题在于ASP.NET Framework没有办法关联Profile数据和特定的用户,除非用户是经过身份鉴别的。
如果希望允许匿名用户修改Profile属性,必须启用ASP.NET Framework的名叫匿名认证(Anonymous Identification)的功能。当匿名认证功能开启时,一个标识(一个GUID)被分配给匿名用户,并保存在持久化的浏览器cookie中。
注解 可以启用不依赖cookie的匿名标识符。不依赖cookie的匿名标识符与不依赖cookie的会话类似:匿名标识符被添加到页面的URL而不是cookie。可以在配置文件中设置anonymousIdenti- fication元素的cookieless属性值为UseURI或AutoDetect,来启用不依赖cookie的匿名标识符。
并且,必须用allowAnonymous标识所有希望匿名用户可以修改的Profile属性。例如,代码清单3-23所示的Web配置文件启用了匿名身份认证,并定义了一个可以被匿名用户修改的Profile属性。
代码清单3-23 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms" />
<anonymousIdentification enabled="true" />
<profile>
<properties>
<add
name="numberOfVisits"
type="Int32"
defaultValue="0"
allowAnonymous="true" />
</properties>
</profile>
</system.web>
</configuration>
代码清单3-23中定义的numberOfVisits属性包含了allowAnonymous属性。注意,Web配置文件同样启用了表单鉴别。当启用表单鉴别,并且没有登录时,用户就是匿名用户。
代码清单3-24所示的页面演示了当匿名身份认证启用时,如何修改Profile属性。
代码清单3-24 ShowAnonymousIdentification.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_PreRender()
{
lblUserName.Text = Profile.UserName;
lblIsAnonymous.Text = Profile.IsAnonymous.ToString();
Profile.numberOfVisits++;
lblNumberOfVisits.Text = Profile.numberOfVisits.ToString();
}
protected void btnLogin_Click(object sender, EventArgs e)
{
FormsAuthentication.SetAuthCookie("Bob", false);
Response.Redirect(Request.Path);
}
protected void btnLogout_Click(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect(Request.Path);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show Anonymous Identification</title>
</head>
<body>
<form id="form1" runat="server">
<div>
User Name:
<asp:Label
id="lblUserName"
Runat="server" />
<br />
Is Anonymous:
<asp:Label
id="lblIsAnonymous"
Runat="server" />
<br />
Number Of Visits:
<asp:Label
id="lblNumberOfVisits"
Runat="server" />
<hr />
<asp:Button
id="btnReload"
Text="Reload"
Runat="server" />
<asp:Button
id="btnLogin"
Text="Login"
OnClick="btnLogin_Click"
Runat="server" />
<asp:Button
id="btnLogout"
Text="Logout"
OnClick="btnLogout_Click"
Runat="server" />
</div>
</form>
</body>
</html>
每次请求代码清单3-24所示的页面时,numberOfVisits这个Profile属性加1并显示。页面包含了3个按钮:Reload,Login和Logout(见图3-8)。
页面同样显示了Profile.UserName属性的值。该属性或者代表当前的用户名,或者代表匿名用户标识符。numberOfVisits这个Profile属性的值在Profile.UserName属性的值之后显示。
点击Reload按钮会很快地重新加载页面,并增加numberOfVisits属性的值。
点击Login按钮,则Profile.UserName变为Bob。numberOfVisits属性被重置。
点击Logout按钮,则Profile.UserName属性切换回显示匿名用户标识符。numberOfVisits属性变回它前一个值。
图3-8 创建匿名用户配置文件
3.3.3 合并匿名用户配置文件
在前一部分,我们已经看到,当用户从匿名切换到鉴别状态时,所有的用户配置信息会丢失。例如,如果在Profile对象中存储了购物车,登录后则所有的购物车项目会丢失。
可以在用户从匿名切换到鉴别状态时,处理Global.asax文件中的MigrateAnonymous事件,预存Profile属性的值。该事件在拥有用户配置的用户登录时触发。
例如,代码清单3-25中,MigrateAnonymous事件自动复制所有的匿名Profile属性到用户当前通过验证的Profile。
代码清单3-25 Global.asax
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
Application["SessionCount"] = 0;
}
void Session_Start(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count + 1;
Application.UnLock();
}
void Session_End(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count - 1;
Application.UnLock();
}
//public void Profile_OnMigrateAnonymous(object sender, ProfileMigrateEventArgs args)
//{
// // Get anonymous profile
// ProfileCommon anonProfile = Profile.GetProfile(args.AnonymousID);
// // Copy anonymous properties to authenticated
// foreach (SettingsProperty prop in ProfileBase.Properties)
// Profile[prop.Name] = anonProfile[prop.Name];
// // Kill the anonymous profile
// ProfileManager.DeleteProfile(args.AnonymousID);
// AnonymousIdentificationModule.ClearAnonymousIdentifier();
//}
//public void Profile_ProfileAutoSaving(object s, ProfileAutoSaveEventArgs e)
//{
// if (Profile.ShoppingCart.HasChanged)
// e.ContinueWithProfileAutoSave = true;
// else
// e.ContinueWithProfileAutoSave = false;
//}
</script>
当用户的匿名标识传递给Profile.GetProfile()方法时,关联用户的匿名Profile会返回。接着,所有匿名Profile的属性被复制到当前Profile。最后,匿名Profile被删除,匿名标识被销毁(如果不销毁匿名标识,则MigrateAnonymous事件在登录认证后的每次页面请求都会被执行)。
3.3.4 从自定义类继承Profile
除了在Web配置文件中定义Profile属性列表,也可以在一个类中定义Profile属性。例如,代码清单3-26所示的类包含了名为FirstName和LastName的两个属性。
代码清单3-26 App_Code/SiteProfile.cs
using System;
using System.Web.Profile;
public class SiteProfile : ProfileBase
{
private string _firstName = "Your First Name";
private string _lastName = "Your Last Name";
[SettingsAllowAnonymous(true)]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
[SettingsAllowAnonymous(true)]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}
注意,代码清单3-26所示的类从BaseProfile类继承。
声明类之后,就可以在Web配置文件中通过从该类继承Profile对象来定义Profile。代码清单3-27所示的Web配置文件使用inherits属性从SiteProfile类继承Profile。
代码清单3-27 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<anonymousIdentification enabled="true" />
<profile inherits="SiteProfile" />
</system.web>
</configuration>
从Web配置文件中继承Profile之后,就可以以正常的方式使用这个Profile了。可以通过访问Profile的属性读写在SieProfile类中定义的任何属性。
注解 随书附带资源中包含一个名叫ShowSiteProfile.aspx的页面,用于显示代码清单3-27中定义的Profile属性。
注解 如果从一个类继承Profile属性,同时在Web配置文件中定义Profile属性,则两组Profile属性将会被合并。
当在一个类中定义Profile属性时,可以使用下面的attribute修饰那些属性:
q SettingAllowAnonymous——用于允许匿名用户读写属性;
q ProfileProvider——用于关联属性到一个特定的Profile提供程序;
q CustomProviderData——用于传递自定义数据到Profile提供程序。
例如,所有声明于代码清单3-28中的SiteProfile类的属性都包含了SettingAllowAnonymous attribute,允许匿名用户读写这些属性。
3.3.5 创建复杂Profile属性
到目前为止,我们已经用Profile属性表达过字符串和整数这些简单类型了。我们也可以使用Profile属性表达诸如自定义购物车这样的复杂类。
例如,代码清单3-28所示的类表示一个简单的购物车。
代码清单3-28 App_Code/ShoppingCart.cs
using System;
using System.Collections.Generic;
using System.Web.Profile;
namespace AspNetUnleashed
{
public class ShoppingCart
{
private List<CartItem> _items = new List<CartItem>();
public List<CartItem> Items
{
get { return _items; }
}
}
public class CartItem
{
private string _name;
private decimal _price;
private string _description;
public string Name
{
get { return _name; }
set { _name = value; }
}
public decimal Price
{
get { return _price; }
set { _price = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public CartItem() { }
public CartItem(string name, decimal price, string description)
{
_name = name;
_price = price;
_description = description;
}
}
}
代码清单3-28中的文件实际上包含了两个类:ShoppingCart类和CartItem类。ShoppingCart类公开了一个CartItem对象的集合。
代码清单3-29所示的Web配置文件定义了一个名为ShoppingCart的Profile属性来表示ShoppingCart类。type属性设为ShoppingCart类的完全标识名称。
代码清单3-29 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile>
<properties>
<add name="ShoppingCart" type="AspNetUnleashed.ShoppingCart" />
</properties>
</profile>
</system.web>
</configuration>
最后,代码清单3-30所示的页面使用了Profile.ShoppingCart属性。ShoppingCart的内容被绑定并显示于一个GridView控件。该页面同样包含了一个表单,用于添加新项目到ShoppingCart中(见图3-9)。
图3-9 在用户配置中存储购物车
代码清单3-30 ShowShoppingCart.aspx
<%@ Page Language="C#" %>
<%@ Import Namespace="AspNetUnleashed" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_PreRender()
{
grdShoppingCart.DataSource = Profile.ShoppingCart.Items;
grdShoppingCart.DataBind();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
CartItem newItem = new CartItem(txtName.Text, decimal.Parse(txtPrice.Text), txtDescription.Text);
Profile.ShoppingCart.Items.Add(newItem);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show ShoppingCart</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView
id="grdShoppingCart"
EmptyDataText="There are no items in your shopping cart"
Runat="server" />
<br />
<fieldset>
<legend>Add Product</legend>
<asp:Label
id="lblName"
Text="Name:"
AssociatedControlID="txtName"
Runat="server" />
<br />
<asp:TextBox
id="txtName"
Runat="server" />
<br /><br />
<asp:Label
id="lblPrice"
Text="Price:"
AssociatedControlID="txtPrice"
Runat="server" />
<br />
<asp:TextBox
id="txtPrice"
Runat="server" />
<br /><br />
<asp:Label
id="lblDescription"
Text="Description:"
AssociatedControlID="txtDescription"
Runat="server" />
<br />
<asp:TextBox
id="txtDescription"
Runat="server" />
<br /><br />
<asp:Button
id="btnAdd"
Text="Add To Cart"
Runat="server" OnClick="btnAdd_Click" />
</fieldset>
</div>
</form>
</body>
</html>
如果希望控制属性存储的复杂度,可以修改Profile属性关联的serializeAs attribute。serializeAs attribute接受下面四个值:
q Binary
q ProviderSpecific
q String
q Xml
当使用SqlProfileProvider时,默认值为ProviderSpecific。也就是说,SqlProfileProvider决定了存储属性的最好方法。通常,简单类型被序列化为字符串,复杂类型使用XML Serializer序列化。
XML Serializer有一个缺点:相比于Binary Serializer它序列化的属性更臃肿。例如,用XML Serializer序列化ShoppingCart类的结果包含于代码清单3-31:
代码清单3-31 序列化购物车
<?xml version="1.0" encoding="utf-16"?>
<ShoppingCart xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<CartItem>
<Name>First Product</Name>
<Price>2.99</Price>
<Description>The First Product</Description>
</CartItem>
<CartItem>
<Name>Second Product</Name>
<Price>2.99</Price>
<Description>The Second Product</Description>
</CartItem>
</Items>
</ShoppingCart>
如果希望用Binary Serializer序列化一个Profile属性(并保存到某些数据库空间),那么需要做两件事。第一,需要在Web配置文件中设定Profile属性应该用Binary Serializer序列化。第二,需要将Profile属性代表的类标记为Serializable。
代码清单3-32所示的修改后的ShoppingClass(名为BinaryShoppingCart)包括了一个Serializable attribute。注意,BinaryShoppingCart和BinaryCartItem类都修饰了Serializable attribute。
代码清单3-32 App_Code/BinaryShoppingCart.cs
using System;
using System.Collections.Generic;
using System.Web.Profile;
namespace AspNetUnleashed
{
[Serializable]
public class BinaryShoppingCart
{
private List<BinaryCartItem> _items = new List<BinaryCartItem>();
public List<BinaryCartItem> Items
{
get { return _items; }
}
}
[Serializable]
public class BinaryCartItem
{
private string _name;
private decimal _price;
private string _description;
public string Name
{
get { return _name; }
set { _name = value; }
}
public decimal Price
{
get { return _price; }
set { _price = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public BinaryCartItem() { }
public BinaryCartItem(string name, decimal price, string description)
{
_name = name;
_price = price;
_description = description;
}
}
}
代码清单3-33所示的Web配置文件中的Profile包括了代表BinaryShoppingCart类的属性。注意,该属性的SerializeAs attribute的值为Binary。如果不包含该attribute,则BinaryShoppingCart将被序列化为XML。
代码清单3-33 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile>
<properties>
<add
name="ShoppingCart"
type="AspNetUnleashed.BinaryShoppingCart"
serializeAs="Binary" />
</properties>
</profile>
</system.web>
</configuration>
注解 随书附带资源包含了ShowBinaryShoppingCart.aspx页面显示BinaryShoppingCart。
3.3.6 自动保存用户配置
一个用户配置在它的某个属性第一次被访问时由用户配置提供程序创建。例如,如果在Page_Load事件处理中使用Profile属性,则用户配置在Page Load事件处理期间被载入。如果在Page_PreRender事件处理中使用Profile属性,则用户配置在Page PreRender事件处理期间被载入。
如果Profile属性被修改,则Profile在页面执行过程的最后被自动保存。ASP.NET Framework只能自动监测某些类型的属性的修改,并不能监测全部类型。一般来说,ASP.NET Framework能自动监测简单类型的修改,但不能监测复杂类型。
例如,如果访问了诸如字符串、整数或者日期等简单类型的属性时,ASP.NET Framework能自动监测这些属性的修改。此时,框架设置Profile.IsDirty属性的值为true。在页面执行的最后,用户配置被标记为脏数据,且被自动保存。
ASP.NET Framework不能监测表示复杂类型的属性的修改。例如,如果用户配置包括了一个表示自定义ShoppingCart类的属性,则ASP.NET Framework没办法判断何时ShoppingCart的内容被修改了。
ASP.NET Framework的过于谨慎。如果访问一个复杂Profile属性——即使只是简单地读该属性——ASP.NET Framework也设置Profile.IsDirty属性为true。换句话说,就是读复杂属性,用户配置也总是在页面执行过程的最后进行保存。
因为在页面执行过程的最后保存用户配置是很大的操作开销,ASP.NET Framework提供了两种方法来控制保存用户配置。
第一,控制保存用户配置被判断过程。代码清单3-34所示的Web配置文件设置autoSaveEnabled属性为false,禁用了用户配置的自动保存功能。
代码清单3-34 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile automaticSaveEnabled="false">
<properties>
<add name="ShoppingCart" type="AspNetUnleashed.ShoppingCart"/>
</properties>
</profile>
</system.web>
</configuration>
禁用自动保存用户配置后,必须显式地在修改用户配置之后调用Profile.Save()方法保存用户配置。例如,代码清单3-35中的btnAdd_Click()方法在添加新项到购物车后显式调用了Profile.Save()。
代码清单3-35 ShowExplicitSave.aspx
<%@ Page Language="C#" %>
<%@ Import Namespace="AspNetUnleashed" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_PreRender()
{
grdShoppingCart.DataSource = Profile.ShoppingCart.Items;
grdShoppingCart.DataBind();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
CartItem newItem = new CartItem(txtName.Text, decimal.Parse(txtPrice.Text), txtDescription.Text);
Profile.ShoppingCart.Items.Add(newItem);
// Explicitly Save Shopping Cart
Profile.Save();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show Explicit Save</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView
id="grdShoppingCart"
EmptyDataText="There are no items in your shopping cart"
Runat="server" />
<br />
<fieldset>
<legend>Add Product</legend>
<asp:Label
id="lblName"
Text="Name:"
AssociatedControlID="txtName"
Runat="server" />
<br />
<asp:TextBox
id="txtName"
Runat="server" />
<br /><br />
<asp:Label
id="lblPrice"
Text="Price:"
AssociatedControlID="txtPrice"
Runat="server" />
<br />
<asp:TextBox
id="txtPrice"
Runat="server" />
<br /><br />
<asp:Label
id="lblDescription"
Text="Description:"
AssociatedControlID="txtDescription"
Runat="server" />
<br />
<asp:TextBox
id="txtDescription"
Runat="server" />
<br /><br />
<asp:Button
id="btnAdd"
Text="Add To Cart"
OnClick="btnAdd_Click"
Runat="server" />
</fieldset>
</div>
</form>
</body>
</html>
除了禁用自动保存用户配置之外,另一种方法是可以在Global.ascx文件的ProfileAutoSaving事件的处理中编写自定义逻辑来控制用户配置的保存。例如,代码清单3-36所示的Global.asax仅在Profile.ShoppingCart.HasChanged属性的值为true时保存用户配置。
代码清单3-36 Global.asax
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
Application["SessionCount"] = 0;
}
void Session_Start(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count + 1;
Application.UnLock();
}
void Session_End(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count - 1;
Application.UnLock();
}
//public void Profile_OnMigrateAnonymous(object sender, ProfileMigrateEventArgs args)
//{
// // Get anonymous profile
// ProfileCommon anonProfile = Profile.GetProfile(args.AnonymousID);
// // Copy anonymous properties to authenticated
// foreach (SettingsProperty prop in ProfileBase.Properties)
// Profile[prop.Name] = anonProfile[prop.Name];
// // Kill the anonymous profile
// ProfileManager.DeleteProfile(args.AnonymousID);
// AnonymousIdentificationModule.ClearAnonymousIdentifier();
//}
//public void Profile_ProfileAutoSaving(object s, ProfileAutoSaveEventArgs e)
//{
// if (Profile.ShoppingCart.HasChanged)
// e.ContinueWithProfileAutoSave = true;
// else
// e.ContinueWithProfileAutoSave = false;
//}
</script>
注解 随书附带资源包括了对应于代码清单3-37所示的Global.asax的ShoppingCart类和ASP.NET页面。类名为ShoppingCartHasChanged.cs,而页面名为ShowShoppingCartHasChanged.aspx。我们需要修改Web配置文件让用户配置继承自ShoppingCartHasChanged类。
3.3.7 从组件访问用户配置
可以在组件内引用HttpContext.Profile属性访问Profile对象。但是,必须在访问它的属性之前,将这个属性的值转换为ProfileCommon对象。
例如,代码清单3-37所示的Web配置文件定义了一个名叫firstName的Profile属性。
代码清单3-37 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<profile>
<properties>
<add name="firstName" defaultValue="Steve" />
</properties>
</profile>
</system.web>
</configuration>
代码清单3-38所示的组件访问了firstName这个Profile属性的值。
代码清单3-38 App_Code/ProfileComponent.cs
using System;
using System.Web;
using System.Web.Profile;
/// <summary>
/// Retrieves first name from Profile
/// </summary>
public class ProfileComponent
{
public static string GetFirstNameFromProfile()
{
ProfileCommon profile = (ProfileCommon)HttpContext.Current.Profile;
return profile.firstName;
}
}
注意 为了避免和本章的其他示例代码冲突,代码清单3-38所示的组件在随书附带资源中被命名为ProfileComponent.cs_listing38。在使用该组件前,需要将它重命名为ProfileComponent.cs。
最后,代码清单3-39所示的页面演示了如何在ASP.NET页面中调用ProfileComponent,读取并显示firstName属性。
代码清单3-39 ShowProfileComponent.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
lblFirstName.Text = ProfileComponent.GetFirstNameFromProfile();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show Profile Component</title>
</head>
<body>
<form id="form1" runat="server">
<div>
First Name:
<asp:Label
id="lblFirstName"
Runat="server" />
</div>
</form>
</body>
</html>
3.3.8 使用配置文件管理器
和Session状态不同,用户配置数据在用户离开应用程序后也不会消失。过一段时间之后,当更多的用户访问了应用程序,大量Profile对象保存的数据就可能变得很大。如果允许匿名用户配置,情况就会变得更糟。
ASP.NET Framework包括了一个名叫ProfileManager的类,用于删除旧的用户配置。该类支持下面的方法:
q DeleteInactiveProfiles——用于删除从某一日期后不再使用的用户配置;
q DeleteProfile——用于删除指定用户名关联的用户配置;
q DeleteProfiles——用于删除和一个用户名数组或ProfileInfo对象数组中的用户对象关联的用户配置;
q FindInactiveProfilesByUserName——用于返回指定用户名关联的并且从某一日期之后不再活动的所有用户配置;
q FindProfilesByUserName——用于获得指定用户名关联的所有用户配置;
q GetAllInactiveProfiles——用于返回某一日期之后不再活动的所有用户配置;
q GetAllProfiles——用于返回每一个用户配置;
q GetNumberOfInactiveProfiles——用于返回指定的日期后不再活动的指定数量的用户配置;
q GetNumberOfProfiles——用于返回指定数量的用户配置。
可以在一个命令行应用程序中使用ProfileManager类,并执行DeleteInactiveProfiles()方法来执行定期的删除不活动的用户配置。
或者,可以在应用程序中创建一个管理页面用于管理用户配置数据。
代码清单3-40所示的页面演示了如何使用ProfileManager类删除不活动的用户配置(见图3-10)。
图3-10 删除不活动的用户配置
代码清单3-40 ManageProfiles.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
DateTime inactiveDate = DateTime.Now.AddMonths(-3);
void Page_PreRender()
{
lblProfiles.Text = ProfileManager.GetNumberOfProfiles(ProfileAuthenticationOption.All).
ToString();
lblInactiveProfiles.Text = ProfileManager.GetNumberOfInactiveProfiles(Profile-
AuthenticationOption.All, inactiveDate).ToString();
}
protected void btnDelete_Click(object sender, EventArgs e)
{
int results = ProfileManager.DeleteInactiveProfiles(ProfileAuthenticationOption.All,
inactiveDate);
lblResults.Text = String.Format("{0} Profiles deleted!", results);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Manage Profiles</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Total Profiles:
<asp:Label
id="lblProfiles"
Runat="server" />
<br />
Inactive Profiles:
<asp:Label
id="lblInactiveProfiles"
Runat="server" />
<br /><br />
<asp:Button
id="btnDelete"
Text="Delete Inactive Profiles"
Runat="server" OnClick="btnDelete_Click" />
<br />
<asp:Label
id="lblResults"
EnableViewState="false"
Runat="server" />
</div>
</form>
</body>
</html>
代码清单3-40所示的页面显示了用户配置的总数和不活动的用户配置的总数。三个月内没有被访问的用户配置被视为不活动的。该页面同样包含了一个Delete Inactive Profiles(删除不活动用户配置)按钮,用于删除旧的用户配置。
3.3.9 配置用户配置提供程序
默认情况下,用户配置数据保存在应用程序的App_Data文件夹下的名叫ASPNETDB.mdf的Microsoft SQL Server Express数据库中。如果需要存储用户配置数据到网络中的另一个数据库,则需要执行下面两个任务:
q 向数据库添加用户配置对象需要的数据库对象;
q 配置应用程序连接到数据库。
可以执行aspnet_regsql命令行工具来添加Profile对象需要的数据库表和存储过程到数据库。aspnet_regsql工具在如下路径:
/WINDOWS/Microsoft.NET/Framework/[version]/aspnet_regsql.exe
注解 如果打开SDK命令行工具,则无需导航到Micorsoft.Net目录就能执行aspnet_regsql工具。
如果不带任何参数执行该工具,则运行ASP.NET SQL Server安装向导。向导将指导用户完成连接数据库,添加需要的数据库对象的过程。
另一个不使用aspnet_regsql工具安装需要的数据库对象的方法是执行下面的两个SQL脚本文件:
/WINDOWS/Microsoft.NET/Framework/[version]/InstallCommon.sql
/WINDOWS/Microsoft.NET/Framework/[version]/InstallProfile.sql
设置好数据库后,需要配置默认的用户配置处理程序连接数据库。代码清单3-41所示的Web配置文件连接到MyServer服务器端上名为MyDatabase的数据库。
代码清单3-41 Web.config
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add
name="conProfile"
connectionString="Data Source=MyServer;Integrated Security=true;database=MyDatabase"/>
</connectionStrings>
<system.web>
<profile defaultProvider="MyProfileProvider">
<properties>
<add name="firstName" />
<add name="lastName" />
</properties>
<providers>
<add
name="MyProfileProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="conProfile"/>
</providers>
</profile>
</system.web>
</configuration>
完成这些配置步骤后,所有的用户配置数据将保存到自定义数据库。
3.3.10 创建自定义用户配置提供程序
用户配置对象使用了提供程序模式。ASP.NET Framework包括了一个简单的用户配置提供程序:SqlServerProfileProvider,它将用户配置数据存储到Microsoft SQL Server数据库。在这一部分,你将了解如何构建自定义用户配置提供程序。
默认的SqlServerProfileProvider的问题是,它序列化整个用户配置到一个大文本,并将这个大文本保存到数据库表的列。也就是说,我们不能使用SQL查询用户配置中的属性。换句话说,默认的SqlServerProfileProvider使得生成用户配置中存储的属性的报表变得极其困难。
在这一部分,我们创建一个新的用户配置提供程序,名为BetterProfileProvider。这个BetterProfileProvider存储每一个Profile属性到一个独立的数据库列。
完整的BetterProfileProvider代码包含在随书附带资源中。
BetterProfileProvider继承自ProfileProvider基类。ProfileProvider基类的两个重要方法必须被覆盖,它们是GetPropertyValues()和SetPropertyValues()方法。这些方法负责为特定用户加载和保存用户配置。
想像一下,我们使用BetterProfileProvider来呈现一个用户配置,包含三个属性:FirstName、LastName和NumberOfVisits。在使用BetterProfileProvider之前,必须创建一个数据库表,包含三个列对应这些Profile属性。另外,数据表必须包含一个int类型名为ProfileID的列。
我们可以使用下面的SQL命令创建需要的数据库表:
CREATE TABLE ProfileData
{
ProfileID Int,
FirstName NVarChar(50),
LastName NVarChar(50),
NumberOfVisits Int
}
接下来,我们创建一个名为Profiles的数据库表。该表用于描述每一个用户配置的属性。可以用下面的SQL命令创建Profiles表:
CREATE TABLE Profiles
(
UniqueID IDENTITY NOT NULL PRIMARY KEY,
UserName NVarchar(255) NOT NULL,
ApplicationName NVarchar(255) NOT NULL,
IsAnonymous BIT,
LastActivityDate DateTime,
LastUpdatedDate DateTime,
)
创建这两个数据库表后,我们就可以使用BetterProfileProvider了。代码清单3-42所示的Web配置文件配置BetterProfileProvider成为默认的用户配置处理程序。
代码清单3-42 Web.config
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add
name="conProfile"
connectionString="Data Source=./SQLExpress;Integrated Security=true;AttachDBFileName=
|DataDirectory|ProfilesDB.mdf;User Instance=true" />
</connectionStrings>
<system.web>
<profile defaultProvider="MyProfileProvider">
<properties>
<add name="FirstName" />
<add name="LastName" />
<add name="NumberOfVisits" type="Int32" />
</properties>
<providers>
<add
name="MyProfileProvider"
type="AspNetUnleashed.BetterProfileProvider"
connectionStringName="conProfile"
profileTableName="ProfileData" />
</providers>
</profile>
</system.web>
</configuration>
注意,BetterProfileProvider被一个connectionStringName和一个profileTableName属性配置。connectionStringName指向包含了我们前面创建的两个数据库表的数据库。profileTableName属性包含了包含用户配置数据的表的名称(该属性的默认值为ProfileData,这里确实不需要它)。
配置BetterProfileProvider之后,就可以用与默认的SqlProifleProvider类似的方法来使用它。例如,代码清单3-43所示的页面显示了FirstName、LastName和NumberOfVisits这些用户配置属性的值,并且允许修改FirstName和LastName属性的值。
注意 BtterProfileProvider有几个重要的限制。它不支持序列化,所以不能用它来处理诸如自定义购物车这样的复杂类型。它也不支持Profile属性默认值。
代码清单3-43 ShowBetterProfileProvider.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_PreRender()
{
Profile.NumberOfVisits++;
lblNumberOfVisits.Text = Profile.NumberOfVisits.ToString();
lblFirstName.Text = Profile.FirstName;
lblLastName.Text = Profile.LastName;
}
protected void btnUpdate_Click(object sender, EventArgs e)
{
Profile.FirstName = txtNewFirstName.Text;
Profile.LastName = txtNewLastName.Text;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show BetterProfileProvider</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Number of Visits:
<asp:Label
id="lblNumberOfVisits"
Runat="server" />
<br />
First Name:
<asp:Label
id="lblFirstName"
Runat="server" />
<br />
Last Name:
<asp:Label
id="lblLastName"
Runat="server" />
<hr />
<asp:Label
id="lblNewFirstName"
Text="First Name:"
AssociatedControlID="txtNewFirstName"
Runat="server" />
<asp:TextBox
id="txtNewFirstName"
Runat="server" />
<br />
<asp:Label
id="lblNewLastname"
Text="Last Name:"
AssociatedControlID="txtNewLastName"
Runat="server" />
<asp:TextBox
id="txtNewLastName"
Runat="server" />
<br />
<asp:Button
id="btnUpdate"
Text="Update"
OnClick="btnUpdate_Click"
Runat="server" />
</div>
</form>
</body>
</html>
BetterProfileProvider的主要好处是,可以对存储于ProfileData表的数据使用SQL进行查询。例如,代码清单3-44所示的页面在一个GridView控件中显示了ProfileData的内容(见图3-11)。如果使用默认的SqlProfileProvider是做不了这个的,因为默认的SqlProfileProvider将用户配置数据保存在一块。
代码清单3-44 BetterProfileProviderReport.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>BetterProfileProvider Report</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Activity Report</h1>
<asp:GridView
id="grdProfiles"
DataSourceID="srcProfiles"
Runat="server" />
<asp:SqlDataSource
id="srcProfiles"
ConnectionString="<%$ ConnectionStrings:conProfile %>"
SelectCommand="SELECT ProfileID,FirstName,LastName,NumberOfVisits
FROM ProfileData"
Runat="server" />
</div>
</form>
</body>
</html>
图3-11 显示用户配置报表