在这篇文章里,我们将一起来讨论Profile Service使用方式的一些细节。
1、ASP.NET Profile基础
这部分简单地描述了ASP.NET Profile的使用,如果已经了解ASP.NET Profile的朋友就可以跳过这部分了。
在ASP.NET中,可以使用Profile,只需在web.config中进行定义即可。例如:
<
system
.web
>
< anonymousIdentification enabled ="true" />
< profile enabled ="true" defaultProvider ="SqlProvider" >
< providers >
< add
name ="SqlProvider"
connectionStringName ="SqlServices"
applicationName ="ProfileBaseApplication"
type ="System.Web.Profile.SqlProfileProvider" />
providers >
< properties >
< add name ="ZipCode" allowAnonymous ="true" />
< add name ="RecentSearchList"
type ="System.Collections.Specialized.StringCollection"
serializeAs ="Xml"
allowAnonymous ="true" />
properties >
profile >
system.web >
< anonymousIdentification enabled ="true" />
< profile enabled ="true" defaultProvider ="SqlProvider" >
< providers >
< add
name ="SqlProvider"
connectionStringName ="SqlServices"
applicationName ="ProfileBaseApplication"
type ="System.Web.Profile.SqlProfileProvider" />
providers >
< properties >
< add name ="ZipCode" allowAnonymous ="true" />
< add name ="RecentSearchList"
type ="System.Collections.Specialized.StringCollection"
serializeAs ="Xml"
allowAnonymous ="true" />
properties >
profile >
system.web >
在运行时,则可以通过当前Context的Profile属性获得当前用户的Profile信息,Profile属性是一个继承ProfileBase的ProfileCommon类实例,根据web.config的中的定义提供了强类型的属性访问。例如:
public
void
Page_Load(
object
sender, EventArgs e)
{
this .Profile.ZipCode = "... " ;
}
{
this .Profile.ZipCode = "... " ;
}
在web.config中也可以定义ProfileGroup。例如:
<
properties
>
< add name ="ZipCode" />
< group name ="Address" >
< add name ="Street" />
< add name ="City" />
< add name ="State" />
< add name ="CountryOrRegion" />
group >
properties >
< add name ="ZipCode" />
< group name ="Address" >
< add name ="Street" />
< add name ="City" />
< add name ="State" />
< add name ="CountryOrRegion" />
group >
properties >
这样就定义了一个ProfileGroup,它会被定义成一个ProfileGroupBase的子类ProfileGroupXXXX(例如上面的定义则是ProfileGroupAddress),然后这个ProfileGroup也提供了相应的强类型属性。这样,就可以通过如下的方式访问到:
public
void
Page_Load(
object
sender, EventArgs e)
{
this .Profile.Address.City = "..." ;
}
{
this .Profile.Address.City = "..." ;
}
Profile是一个非常容易使用的东西,我们能够为其赋值,它能够使用指定Provider自动地进行保存。另外在web.config文件中,我们也能够为Profile的的各项值定义各种属性,例如是否能够被匿名用户使用,是否只读,甚至让ProfileCommon类继承自定义的Profile子类。详细信息,请感兴趣的朋友们参考MSDN相关章节。
2、配置ASP.NET AJAX Profile Service
查看ASP.NET AJAX提供的配置文件,在
<
configSections
>
< sectionGroup name ="microsoft.web" type ="Microsoft.Web.Configuration.MicrosoftWebSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< sectionGroup name ="scripting" type ="Microsoft.Web.Configuration.ScriptingSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< sectionGroup name ="webServices" type ="Microsoft.Web.Configuration.ScriptingWebServicesSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< section name ="jsonSerialization" type ="Microsoft.Web.Configuration.ScriptingJsonSerializationSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission ="false" />
<section name="profileService" type="Microsoft.Web.Configuration.ScriptingProfileServiceSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" />
< section name ="authenticationService" type ="Microsoft.Web.Configuration.ScriptingAuthenticationServiceSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission ="false" />
sectionGroup >
sectionGroup >
sectionGroup >
configSections >
< sectionGroup name ="microsoft.web" type ="Microsoft.Web.Configuration.MicrosoftWebSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< sectionGroup name ="scripting" type ="Microsoft.Web.Configuration.ScriptingSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< sectionGroup name ="webServices" type ="Microsoft.Web.Configuration.ScriptingWebServicesSectionGroup, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" >
< section name ="jsonSerialization" type ="Microsoft.Web.Configuration.ScriptingJsonSerializationSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission ="false" />
<section name="profileService" type="Microsoft.Web.Configuration.ScriptingProfileServiceSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" />
< section name ="authenticationService" type ="Microsoft.Web.Configuration.ScriptingAuthenticationServiceSection, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission ="false" />
sectionGroup >
sectionGroup >
sectionGroup >
configSections >
请注意加粗的部分,它说明在web.config中,我们可以配置Profile Service的一些属性。根据分析代码(事实上从web.config的注释中也能得察一二),可以得到配置的使用方式。例如:
<
profileService
enabled
="true"
writeAccessProperties ="ZipCode, Address.Street"
readAccessProperties ="Address.State" />
writeAccessProperties ="ZipCode, Address.Street"
readAccessProperties ="Address.State" />
如果将enabled设为false,则在使用Profile Service时会抛出异常。writeAccessProperties和readAccessProperties属性(在这里感谢Cat Chen的提醒,请注意在Beta1版本中,web.config中的注释是错误的)分别定义了可以使用Profile Service设置和获取的属性列表。由于System.Web.UI.WebControls.StringArrayConverter的作用,能够使逗号分割的字符串与字符串数组之间进行转换。另外,能够使用“.”来表述Group中的某个Profile信息,例如“Address.State”则表示了Address这个Group下的State信息。
请注意,虽然定义了writeAccessProperties和readAccessProperties,但是从客户端设法设置或读取“不被允许”的Profile属性也不会出现错误,只是设置或者读取无法成功而已。
3、使用Profile Service读取Profile信息
从客户端获取Profile信息的主要逻辑如下:
- 调用Sys.Services.ProfileService.load(propertyNames, loadCompletedCallback, failedCallback, userContext)。
- 如果propertyNames为空,则调用服务器端GetAllPropertiesForCurrentUser方法,否则使用_clonePropertyNames函数剔出propertyNames中重复的属性名,然后调用服务器端_GetPropertiesForCurrentUser方法。
- 服务器端根据得到的参数,从Profile信息中提取信息并构造出一个Dictionary
数组。 - 如果成功,则根据服务器端返回对象,调用_unflattenProperties方法将该对象转换为Profile属性以及Profile Group,最后调用loadCompletedCallback回调函数。
- 如果失败,调用failedCallback函数。
关于第2步中的方法访问,ASP.NET AJAX中的Application Services都是基于ASP.NET AJAX的客户端Web Services访问能力的。在默认情况下Profile Service会访问“ScriptServices/Microsoft/Web/Profile/ProfileService.asmx”,由于不存在这个文件,ASP.NET AJAX将会查找Microsoft.Web.Profile.ProfileService类并调用该类的方法,这个就是“Assembly-based Web Services Access”。不过令人扼腕的是,虽然在当前版本的ASP.NET AJAX中还保留了这个机制,但是根据“白皮书”和可靠来源基本上可以确定这个功能会在下一个版本中被移除。这让我感到非常不可思议。个人认为,ASP.NET AJAX的一个重要特点,就是提供了一个能够扩展的模型。对于客户端来说,无论Control,Action,Validator乃至Behavior,几乎方方面面都能有规可循地进行扩展。很自然,服务器端也是,可能ASP.NET AJAX服务器端中最典型的应用就是Control Tookit了。其良好的复用性让人可谓眼前一亮。既然封装成了一个组件,则分发和部署则成为了一件重要的事情。许多ASP.NET AJAX组件必须通过客户端和服务器端的配合,然后使用客户端访问服务器端的Web Services方法来执行。如果需要在程序集中进行输出Javascript,则可以使用内嵌资源。但是如果客户端需要访问的Web Services文件是独立于程序集之外的话,那么还是增加了分发和部署的难度,甚至在开发与维护方面也变得需要管理多个对象,非常麻烦。而“Assembly-based Web Services Access”就能很好地解决这个问题。这样优秀的特性为什么会被取消?
在第4步中,从服务器端得到的数据是以如下JSON形式出现的:
{
" ZipCode " : "..." ,
" Address.City " : "..." ,
" Address.State " : "..."
}
" ZipCode " : "..." ,
" Address.City " : "..." ,
" Address.State " : "..."
}
Profile Group也是通过“.”分割来表示的。很自然,如果在load方法的第一个参数需要传递一个Profile Group中的一个属性时,也需要使用这样的表示方法。
另外,loadCompletedCallback和failedCallback函数的签名分别如下:
//
propertyNumber:成功加载的Profile数量,同一个Profile Group下的属性会分别计算
// userContext:调用load时传入的userContext
// methodName:方法名,值为"Sys.Services.ProfileService.load"
function loadCompletedCallback(propertyNumber, userContext, methodName)
{
...
}
// error:错误对象,存放了错误信息
// userContext:调用load时传入的userContext
// methodName:方法名,值为"Sys.Services.ProfileService.load"
function failedCallback(error, userContext, methodName)
{
...
}
// userContext:调用load时传入的userContext
// methodName:方法名,值为"Sys.Services.ProfileService.load"
function loadCompletedCallback(propertyNumber, userContext, methodName)
{
...
}
// error:错误对象,存放了错误信息
// userContext:调用load时传入的userContext
// methodName:方法名,值为"Sys.Services.ProfileService.load"
function failedCallback(error, userContext, methodName)
{
...
}
事实上,这两个回调函数都可以在调用load时不指定,这样就会使用默认的回调函数和默认的userContext,它们可以通过以下方法设置:
Sys.Services.ProfileService.set_loadCompleteCallback(callback);
Sys.Services.ProfileService.set_defaultFailedCallback(callback);
Sys.Services.ProfileService.set_defaultFailedCallback(callback);
在load方法调用成功后,就可以在客户端获得使用Profile的值了,例如:
var
zipCode
=
Sys.Services.ProfileService.properties.ZipCode;
var city = Sys.Services.ProfileService.properties.Address.City;
var state = Sys.Services.ProfileService.properties.Address.State;
var city = Sys.Services.ProfileService.properties.Address.City;
var state = Sys.Services.ProfileService.properties.Address.State;
4、使用Profile Service设置Profile信息
从客户端设置Profile信息的主要逻辑如下:
- 调用Sys.Services.ProfileService.save(propertyNames, saveCompletedCallback, failedCallback, userContext) 。
- 调用_flattenProperties,根据propertyNames将Sys.Services.ProfileService.properties中的信息变为JSON形式。如果propertyNames为null,则表示保存所有Profile属性。
- 调用服务器端SetPropertiesForCurrentUser方法,服务器端会将JSON字符串转变为IDictionary
,并保存在Profile中。 - 如果成功,则调用saveCompletedCallback回调函数。
- 如果失败,则调用failedCallback回调函数。
在第2步中,如果需要表示Profile Group中的属性,依旧通过“.”来分割。如果需要修改Profile信息,直接修改properties对象即可。如果需要创建Profile Group,则需要使用到客户端的Sys.Services.ProfileGroup类。例如:
Sys.Services.ProfileService.properties.ZipCode
=
"...
"
;
if ( ! Sys.Services.ProfileService.properties.Address)
{
Sys.Services.ProfileService.properties.Address = new Sys.Services.ProfileGroup();
}
Sys.Services.ProfileService.properties.Address.City = "... " ;
if ( ! Sys.Services.ProfileService.properties.Address)
{
Sys.Services.ProfileService.properties.Address = new Sys.Services.ProfileGroup();
}
Sys.Services.ProfileService.properties.Address.City = "... " ;
如果某Profile属性为复杂类型,则使用JSON形式在客户端赋值即可。
至于回调函数的签名,和load一模一样,只是methodName的值变为了"Sys.Services.ProfileService.save"。
在这篇文章中,我们简单讨论了ASP.NET AJAX的使用方法。在下一篇文章中,我们将一起来看一下配合ScriptManager来自定义Profile Service。