代码改变世界

深入Atlas系列:探究Application Services(1) - Profile Service分析与使用

2006-11-02 23:43  Jeffrey Zhao  阅读(...)  评论(... 编辑 收藏
  ASP.NET AJAX提供了Profile Service,允许开发人员异步地从服务器端访问Profile信息。从RTM开始,客户端的Profile Service还提供了对于Profile Group的支持,因此可以说已经相当成熟了。那么对于Profile Service的细节,是否大家都了解了呢?从ScriptManager的使用上来看,ProfileService是能够扩展的,那么应该如何扩展呢?细心的朋友们应该也发现了,在web.config中也增加了对于Profile Service的配置,那么这些配置应该如何使用呢?

在这篇文章里,我们将一起来讨论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>

在运行时,则可以通过当前Context的Profile属性获得当前用户的Profile信息,Profile属性是一个继承ProfileBase的ProfileCommon类实例,根据web.config的中的定义提供了强类型的属性访问。例如:
public void Page_Load(object sender, EventArgs e)
{
    
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>

这样就定义了一个ProfileGroup,它会被定义成一个ProfileGroupBase的子类ProfileGroupXXXX(例如上面的定义则是ProfileGroupAddress),然后这个ProfileGroup也提供了相应的强类型属性。这样,就可以通过如下的方式访问到:
public void Page_Load(object sender, EventArgs e)
{
    
this.Profile.Address.City = "...";
}

Profile是一个非常容易使用的东西,我们能够为其赋值,它能够使用指定Provider自动地进行保存。另外在web.config文件中,我们也能够为Profile的的各项值定义各种属性,例如是否能够被匿名用户使用,是否只读,甚至让ProfileCommon类继承自定义的Profile子类。详细信息,请感兴趣的朋友们参考MSDN相关章节。


2、配置ASP.NET AJAX Profile Service

查看ASP.NET AJAX提供的配置文件,在<configSections />可以看到如下定义:
<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" />

如果将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信息的主要逻辑如下:
  1. 调用Sys.Services.ProfileService.load(propertyNames, loadCompletedCallback, failedCallback, userContext)。
  2. 如果propertyNames为空,则调用服务器端GetAllPropertiesForCurrentUser方法,否则使用_clonePropertyNames函数剔出propertyNames中重复的属性名,然后调用服务器端_GetPropertiesForCurrentUser方法。
  3. 服务器端根据得到的参数,从Profile信息中提取信息并构造出一个Dictionary<string, object>数组。
  4. 如果成功,则根据服务器端返回对象,调用_unflattenProperties方法将该对象转换为Profile属性以及Profile Group,最后调用loadCompletedCallback回调函数。
  5. 如果失败,调用failedCallback函数。
  需要注意的是,如果需要加载所有的Profile,在第1步中则必须使用null作为第一个参数,如果使用了一个空数组,则不会加载任何Profile属性。

关于第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" : "..."
}

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)
{
    ...
}

事实上,这两个回调函数都可以在调用load时不指定,这样就会使用默认的回调函数和默认的userContext,它们可以通过以下方法设置:
Sys.Services.ProfileService.set_loadCompleteCallback(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;



4、使用Profile Service设置Profile信息

从客户端设置Profile信息的主要逻辑如下:
  1. 调用Sys.Services.ProfileService.save(propertyNames, saveCompletedCallback, failedCallback, userContext) 。
  2. 调用_flattenProperties,根据propertyNames将Sys.Services.ProfileService.properties中的信息变为JSON形式。如果propertyNames为null,则表示保存所有Profile属性。
  3. 调用服务器端SetPropertiesForCurrentUser方法,服务器端会将JSON字符串转变为IDictionary<string, object>,并保存在Profile中。
  4. 如果成功,则调用saveCompletedCallback回调函数。
  5. 如果失败,则调用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 
= "...";

如果某Profile属性为复杂类型,则使用JSON形式在客户端赋值即可。

至于回调函数的签名,和load一模一样,只是methodName的值变为了"Sys.Services.ProfileService.save"。



在这篇文章中,我们简单讨论了ASP.NET AJAX的使用方法。在下一篇文章中,我们将一起来看一下配合ScriptManager来自定义Profile Service。