本文英文原版及代码下载:
http://aspnet.4guysfromrolla.com/articles/101106-1.aspx
考察ASP.NET 2.0的Membership, Roles,Profile - Part 6
导言:
除了用户帐户的username, passsword, email, security question和 answer等,在实际的程序中我们还可能添加额外的信息,比如我们可能需要用户指定一个签名、主页URL、以及IM address等.
使用Membership model,有2种方法来将用户帐户与额外的信息联系起来.第一种,具有最大的灵活性也要做最多的准备工作——为额外信息创建自定义数据存储.如果你使用的是SqlMembershipProvider,那就意味着要创建一个额外的表,将aspnet_Users表的UserId值作为主键.以一个在线messageboard为例,该表可为forums_UserProfile,具有的列为UserId(将aspnet_Users表的UserId作为主键和外键)、HomepageUrl、 Signature、 IMAddress.
另外,我们还可以使用ASP.NET 2.0 Profile system来存储用户的具体信息.Profile system允许定义与用户相关的属性。一旦定义后,开发者就可以通过编程来读取和访问这些属性的值.与Membership、Roles类似,Profile system也是基于provider model,默认情况下,.NET Framework有一个SqlProfileProvider class类,它使用一个SQL Server数据库表(aspnet_Profile)来作为其存储备份(backing store).
在本文,我们将考察Profile system——如何定义用户属性以及如何从ASP.NET页面来编程访问它们。同时我们还将看到.NET 2.0里的SqlProfileProvide使用自定义的profile provider。
概述Profile System
ASP.NET 2.0里面的Membership system设计来创建处理用户帐户的标准API.虽然Membership system有与用户相关的核心属性—username, password, email address等,但是我们还经常需要获取每个用户的其它信息.而且每个程序之间的额外信息还彼此不同.
为此,微软创建Profile system来处理这些额外的用户属性.Profile system允许将这些用户属性定义在Web.config文件并将其值存储在某些数据存储里.默认的Profile provider - SqlProfileProvider,将这些属性值存储在一个叫aspnet_Profile的SQL Server数据库表里.
当使用Profile system时,我们要记住其唯一的目的是作为一种定义一系列用户属性的方法.再通过某个具体的provider,将这些属性值存储在某些backing store里.
定义用户属性
用Profile system,我们必须要在Web.config文件里定义清楚.对每个属性我们要指定其name, 数据类型,以及数据应如何序列化(serialized).如下为4个serialization选项:
.ProviderSpecific(默认)—Profile provider要决定如何序列化属性值.
.String—属性值将转换成一个字符串形式
.Xml—属性值将转换成一个XML形式
.Binary—属性值将转换成一个二进制形式
你选择哪种serialization方法取决于变量的类型.如果允许将属性值转化为一个String,那么我们就要让provider将数据序列化为一个String;对XML和Binary类型,以此类推.
定义在Profile system的属性可为简单的标量类型(scalar),进而在聚合(grouped into)成复杂的类型,或已存在的复杂类型(例如一个类)。每个Profile属性的names, types,和serialization都定义在<profile> 元素的<properties>节点.比如,假象我们要用户指定一个主页的URL,我们可以添加如下的属性:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
...
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
...
</properties>
</profile>
</system.web>
</configuration>
<add>元素添加了一个名为HomepageUrl,类型为String,serialized为String的属性。除此之外我们还可以在<add>元素添加额外的属性.
标量类型的属性可以通过使用<group>元素进行聚合。比如,除了HomepageUrl属性外,我们还想获取用户的实际位置、生日、选用的编程语言等.这些信息可以分别为3个标量值,也可以进行聚合.如下的代码显示如何将这3个属性值聚合为一个名为Bio的组.
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
<group name="Bio">
<add name="BirthDate" type="DateTime" serializeAs="Xml" />
<add name="Location" type="String" />
<add name="ProgrammingLanguageOfChoice" type="ProgrammingLanguages" />
</group>
...
</properties>
</profile>
上述代码里,BirthDate属性(类型为DateTime)被选作序列化为XML,而Location 和 ProgrammingLanguageOfChoice属性未指定serializeAs属性,那意味着Profile provider将自行决定如何序列化这2个属性.注意ProgrammingLanguageOfChoice属性的类型,该类型是我们在App_Code文件夹里创建的。代码如下:
Public Enum ProgrammingLanguages As Integer
NoneSelected = 0
VB = 1
CSharp = 2
JSharp = 3
End Enum
此外,Profile属性的类型还可以为一个自定义类,在本文下载内容里,你将发现一个很简单的Address class类(也是在App_Code文件夹):
<Serializable()> _
Public Class Address
Public Address1 As String
Public Address2 As String
Public City As String
Public State As String
Public Zip As Integer
End Class
定义了上述类后,我们可以添加一个Address类型的属性,如下:
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
<group name="Bio">
<add name="BirthDate" type="DateTime" serializeAs="Xml" />
<add name="Location" type="String" />
<add name="ProgrammingLanguageOfChoice" type="ProgrammingLanguages" />
</group>
<add name="BillingAddress" type="Address" serializeAs="Xml" />
<add name="ShippingAddress" type="Address" serializeAs="String" />
</properties>
</profile>
上述代码里,BillingAddress 和 ShippingAddress属性都为Address类型.
在代码里处理Profile属性
完成Profile属性的定义后,ASP.NET引擎自动地创建一个ProfileCommon class类,该类包含的属性与我们在Web.config文件里定义的属性相匹配.当我们在Web.config文件里修改这些属性后,该类将重新创建并自动处理当前登录用户的profile.(你也可以设置profile对匿名用户的支持,不过这不是本文的主题,我们将在后面的文章探讨).
可以在一个ASP.NET页面里通过HttpContext对象的Profile属性来访问ProfileCommon class类。比如,要在一个ASP.NET页面里读取当前登录用户的HomepageUrl属性,仅仅只需要使用Profile.HomepageUrl即可. 实际上,只要你输入Profile再键入一个“.”智能感知功能将带出各种属性。酷吗?实际上聚合功能同样工作地很好,输入Profile再键入一个“.”,你将看到其中一个属性为Bio.输入Bio再键入一个“.”,那3个标量属性就出现在智能感知界面里(BirthDate, Location, and ProgrammingLanguageOfChoice).
图1
本文下载代码里有3个页面值得考察,第一个页面为/UsersOnly/Default.aspx页面,显示了当前登录用户的Profile信息.第二个页面为/UsersOnly/UpdateProfile.aspx,允许用户更改其Profile数据.第三个页面演示了如何使用Profile属性的GetProfile("username")方法来获取其它,非当前登录用户的Profile数据.页面/UserList.aspx列出了系统里的所有用户的membership信息(username, email等),以及HomepageUrl属性值.
指定一个Profile Provider
如果你没有显式地指定一个Profile provider,那么默认使用的是SqlProfileProvider,它将用户的property值存储在aspnet_Profile表里,但效率比较低,该表的构架如下:
图2
对于给定的记录,PropertyNames列包含的是每个Profile属性的名称,根据其序列化形式的不同,将其值存储在PropertyValuesString 或 PropertyValuesBinary列.如果是以String或 XML形式则存储在PropertyValuesString列;如果是以Binary形式则在PropertyValuesBinary列.
就我们定义的Profile属性而言,该某个用户的PropertyNames列的值可能为:
Bio.ProgrammingLanguageOfChoice:S:0:92:HomepageUrl:S:92:30:ShippingAddress:B:0:212:...
如你所见,每个property name的形式为PropertyName:B|S:StartIndex:Length,其中B 或 S指出了该属性的值是存储在PropertyValuesString(S)列 还是在 PropertyValuesBinary(B)列的.比如,Bio.ProgrammingLanguageOfChoice属性的值存储在PropertyValuesString (S)列,起始位置为0,长度为92.而HomepageUrl属性的值也存储在PropertyValuesString (S)列,起始位置为92,长度为30.
查看PropertyValuesString列,我们看到:
<?xml version="1.0" encoding="utf-16"?><ProgrammingLanguages>CSharp</ProgrammingLanguages>http://www.datawebcontrols.com...
ProgrammingLanguageOfChoice的值已经被序列化为XML形式,开始位置为0,长度为92;同样,HomepageUrl属性开始位置为92,长度为30.(而ShippingAddress属性为Binary形式,我们可以在PropertyValuesBinary列里找到其值,开始位置为0.长度为212)
SqlProfileProvider自动的将Profile属性迁移到数据库.而SqlProfileProvider用到的表和存储过程是在贯彻SqlMembershipProvider时自动创建的(见Part 1).
SqlProfileProvider的局限性
由于SqlProfileProvider将所有的属性值压缩到一行的1到2列,每次对任何一个属性值的读取都要触动所有的属性值;另外数据的格式也使的我们很难查询和返回这些属性信息。假如我们想知道有多少用户使用C#(该信息存储在PropertyValuesString列),我们首先需要将其数据格式转换成正常的数据结构.
作为SqlProfileProvider的替代,我们可以使用微软雇员Hao Kung开发的免费的Table Profile Provider(http://www.asp.net/downloads/sandbox/table-profile-provider-samples/)。该provider将每个Profile属性存储在各自的列里。另外,我们开可以创建自己的Profile provider.
结语:
除了username, password, email address这些基本的用户信息外,如果你还想获取更多的用户信息,你要么使用自定义的解决方案(创建你自己的数据存储,写代码来对数据存储进行数据读写),要么使用Profile system. 它允许页面开发者在Web.config文件里定义一系列的用户属性,这些信息自动的转换为一个类(ProfileCommon),并可以通过HttpContext class类的Profile属性来进行访问.
ASP.NET 2.0默认的Profile provider为SqlProfileProvider,它将Profile属性值存储在一个SQL Server数据库的aspnet_Profile表里.我们只需要通过Profile属性来读写用户的Profile数据.
祝编程快乐!