老赵点滴


  先做人,再做技术人员,最后做程序员。
  我的理想:“让外国人看中国人写的技术书籍和文章”。Try as I might
posts - 290, comments - 10754, trackbacks - 146, articles - 6
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
  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。

Feedback

#1楼 [楼主]   回复  引用  查看    

2006-11-02 23:50 by Jeffrey Zhao      
不伦不类的文章……

#2楼    回复  引用  查看    

2006-11-02 23:50 by TerryLee      
唉,最近要研究的东西好多啊……

项目组也忙了起来-_-

#3楼 [楼主]   回复  引用  查看    

2006-11-02 23:52 by Jeffrey Zhao      
@TerryLee
哎,是阿,快神经衰弱了……
培训准备得如何了阿?不知道模版之类的什么时候会有……

#4楼    回复  引用  查看    

2006-11-02 23:54 by TerryLee      
@Jeffrey Zhao
还没准备呢,白天项目忙,晚上写点东西就没时间了:)

模版问问田兄吧,这几天我也没顾得上问

#5楼 [楼主]   回复  引用  查看    

2006-11-02 23:56 by Jeffrey Zhao      
@TerryLee
呵呵,坚持……

#6楼    回复  引用  查看    

2006-11-03 01:13 by Cat Chen      
这个东西你有没有试运行过?据我所知,这段写法不会导致编译错误,但会导致运行错误:
<profileService enabled="true"
setProperties="ZipCode, Address.Street"
getProperties="Address.State" />

而且,正如我之前所说,要用fiddler才能知道错误,而错误的原因你用reflector打开CTP和Beta1的代码看看就知道了,CTP是这样的:
Microsoft.Web.Configuration.ProfileServiceSection {
public string[] GetProperties {get;set;}
public string[] SetProperties {get;set;}
}
然而Beta1是这样的:
Microsoft.Web.Configuration.ScriptingProfileServiceSection {
public string[] ReadAccessProperties {get;set;}
public string[] WriteAccessProperties {get;set;}
}

然而在Beta1自动为你项目添加的web.config中,还是按老的写法来提供注释,这显然就是一个巨大的bug。

我发现这个漏洞以后,准备再积累一些ProfileService的使用经验之后就写出来的,既然你写了我就可以懒得写啦,哈哈……仅仅做bug report。我写东西很慢很慢,因为总是喜欢一般对照着MSDN和Reflector看,看看MSDN哪里又在胡乱吹了,然后一边做实验验证事实。

#7楼 [楼主]   回复  引用  查看    

2006-11-03 01:35 by Jeffrey Zhao      
@Cat Chen
阿哈,你仔细的。我本来准备看Reflector里到底写成了什么样,但是一看注释里有就轻心了,哈哈。
其实代码里是ReadAccessPropeties或getProperties无关紧要,关键的是属性上面的CustomAttribute是[ConfigurationProperty("readAccessProperties", DefaultValue=null)]还是 [ConfigurationProperty("getProperties", DefaultValue=null)]。
我改过来了。:)


#8楼    回复  引用  查看    

2006-11-03 08:39 by 阿不      
简单的讲,就是提供给客户端调用的两个内置的WebService,Profile和Authentication,这两个WebService都定义在Microsoft.Web.Extension.dll。如果需要扩展,我们可以重写两个WebService,然后在ScriptManager中指定就行了,但是没法从内置的那两个继承下来,它们是被定义为internal sealed的类。

#9楼 [楼主]   回复  引用  查看    

2006-11-03 10:06 by Jeffrey Zhao      
@阿不
继不继承倒无所谓,关键是取消了Assembly-based Web Services Access让我很无语。

#10楼    回复  引用  查看    

2006-11-03 11:36 by Dflying Chen      
使用Profile Service读取Profile信息时候需要在ScriptManager中声明什么么?
或者,需不需要在页面中添加ProfileService控件?

#11楼 [楼主]   回复  引用  查看    

2006-11-03 11:45 by Jeffrey Zhao      
@Dflying Chen
不需要的,只要在web.config里enabled="true"就可以了,记得默认是false。

#12楼    回复  引用  查看    

2006-11-03 12:41 by Cat Chen      
@Jeffrey Zhao
是啊,关键看Attribute,不过应该不会有人用两个名字命名同一样东西吧,这是一条很基本的代码书写规范啊。

#13楼    回复  引用  查看    

2006-11-03 12:50 by Cat Chen      
@阿不
不能继承要完全重做是很麻烦的,如果你需要做一个兼容任意Profile配置的WebService的话。

当你想扩展的时候,你就会发现问题了——我们平时使用的更新方法名为:
public int SetPropertiesForCurrentUser(IDictionary<string, object> values)
参数是一个字典,string是Property的名称,而object是Property的值。但Property的实值不是object啊,这个Atlas的JSON转换器可不管,给你的就是object。而且这个object不是转换好的object,而是Jeffrey之前面某篇WebService解释中说到的嵌套List<>/Dictionary<>。

然后,ProfileService根据web.config中声明某一个Property的类型(例如MyComplexClass)来完成最终的转换,这个转换用的自然也是internal的方法。如果你自己编写一个ProfileService,你就要自己做这些转换,显然不是简单的事情。

#14楼 [楼主]   回复  引用  查看    

2006-11-03 13:53 by Jeffrey Zhao      
@Cat Chen
其实为了向前兼容的话,这样的事情也是必须作的,不过现在的ASP.NET AJAX可不管这些。

至于自己写Profile Service时,拿到的value参数的确是嵌套的IDictionary和IList,但是可以使用ASP.NET AJAX里提供的JavaScriptSerializer类来转换,这个类是我们在服务器端进行序列化/反序列化的唯一入口,提供的功能已经有很多了,完全足够,呵呵。

#15楼 [楼主]   回复  引用  查看    

2006-11-03 13:57 by Jeffrey Zhao      
public T ConvertToType<T>(object obj);
可以使用JavaScriptSerializer类的这个方法,它的实现只有一行代码:
public T ConvertToType<T>(object obj)
{
  return (T) ObjectConverter.ConvertObjectToType(obj, typeof(T), this);
}

Profile Service就是用了ObjectConverter.ConvertObjectToType这个内部方法。

#16楼    回复  引用  查看    

2006-11-03 19:20 by 阿不      
@Cat Chen
@Jeffrey Zhao
序列化和反序列化确实是一件很麻烦的事情,我一看它内部的序列化算法,头都大了。ObjectConverter.ConvertObjectToType这个方法仍然是内部的,我们无法调用到。最理想的就是它给我们提供一个做好基本功能的WebService抽象,供我们扩展。

#17楼    回复  引用  查看    

2006-11-03 19:22 by 阿不      
@Jeffrey Zhao
虽说它的序列化接口都对外公布了,但是似乎我们还需要做很多工作。看看ConvertObjectToTypeInternal这个方法就够吓人了。

#18楼 [楼主]   回复  引用  查看    

2006-11-03 19:28 by Jeffrey Zhao      
@阿不
我基本上看完了这部分的代码,觉得该有的功能都已经有了,还有什么需要我们自己做呢?

#19楼    回复  引用  查看    

2006-11-03 22:29 by 阿不      
@Jeffrey Zhao
我是说,需要序列化,反序列化的的话,似乎还需要做其它的工作。

另外,现有的功能不一定就能完全满足实际的需要,有的时候可能要对现有功能进行扩展和修改的。

#20楼 [楼主]   回复  引用  查看    

2006-11-04 00:12 by Jeffrey Zhao      
@阿不
我总觉得现有的功能能满足几乎所有情况了,实在特殊的也没有办法……

#21楼 [楼主]   回复  引用  查看    

2006-11-04 03:30 by Jeffrey Zhao      
不过JavaScriptSerializer.ConvertToType<T>只有范型方法的确有缺陷:如果在程序里知道了一个Type,却不能简单地调用它,只能通过反射……

#22楼    回复  引用  查看    

2006-11-04 08:54 by 小蜗牛      
en ,书写规范ms真的很重要。

#23楼    回复  引用    

2007-08-21 12:10 by hmx [未注册用户]
如果我要更新在web.config文件中配置的profile数据值,也就是验证数据更新是否成功,要怎么才能知道啊。请帮忙一下,谢谢。

#24楼 [楼主]   回复  引用  查看    

2007-08-21 22:01 by Jeffrey Zhao      
@hmx
对不起,我没有理解您的意思……

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-11-03 01:40 编辑过


相关链接: