Hello World
Spiga

深入Atlas系列:探究Application Services(3) - 自定义客户端Profile Service支持

2006-11-05 21:32 by 老赵, 3368 visits
  如果不能在客户端进行自定义的话,Profile Service的自定义能力还是远远不够的。虽然Profile Service没有提供一种“官方”的客户端自定义支持,不过事实上“自定义”能力“天然”地存在与客户端里。为什么?因为整个客户端是由JavaScript实现的,这种灵活的语言使得我们能够在一定程度上自由地修改客户端的行为。一般来说,在客户端扩展Profile Service主要有两种方法:


一、使用自定义类替换Sys.Services.ProfileService对象

一般来说,这是最容易想到的办法。我们可以写一个类继承Sys.Services._ProfileService类(这个类完全通过prototype扩展,因此对于继承非常友好),甚至完全重写一个类,这个一般就看具体情况了。假设我们已经定义了这么一个类“Jeffz.Services.ProfileService”,并将其包含在MyProfile.Service.js中,就要开始使用了。那么还要注意些什么呢?

需要注意的就是顺序,我们一般会使用ScriptManager引入该JS,如下:
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="false">
    
<Scripts>
        
<asp:ScriptReference Path="MyProfileService.js" />
    
</Scripts>
    
<ProfileService LoadProperties="ZipCode, Address.City" Path="MyProfile.asmx"/>
</asp:ScriptManager>

我们为ProfileService节点加上了LoadProperties属性,表明需要预加载Profile中的ZipCode和Address这个Profile Group下的City属性。另外,我们将EnablePartialRendering属性设为了False,避免出现多余的代码。于是,生成如下的代码如下:
<script src="/Value-Add-WebSite/WebResource.axd?d=...;t=..." type="text/javascript"></script>
<script type="text/javascript">
<!--
Sys.Services.ProfileService.set_path('/MyProfile.asmx');
Sys.Services.ProfileService.properties 
= Sys.Serialization.JavaScriptSerializer.deserialize('{\"ZipCode\":\"\"}');
Sys.Services.ProfileService.properties.Address 
= new Sys.Services.ProfileGroup(Sys.Serialization.JavaScriptSerializer.deserialize('{\"City\":\"\"}'));
Sys.Services.AuthenticationService._set_authenticated(
true);
// -->
</script>

<script src="MyProfileService.js" type="text/javascript"></script>

第一行引入的是MicrosoftAjax.js,它之中定义了ASP.NET AJAX中默认的ProfileService,而紧接着就是对于ProfileService的使用:设定其Path以及预加载的Properties。在引入之后千万不能忘了要将这些信息进行保留。但是这两者之间无法插入任何代码,因此我们可以在MyProfileService.js里添加如下的代码,以保留这些信息:
var path = Sys.Services.ProfileService.get_path();
if (!path)
{
    path 
= Sys.Services._ProfileService.WebServicePath;
}
var properties = Sys.Services.ProfileService.properties;

var newInstance = new Jeffz.Services.ProfileService();
newInstance.set_path(path);
newInstance.properties 
= properties;
Sys.Services.ProfileService 
= newInstance;

当然,可能代码会根据实际情况略有不同,但是注意JavaScript引入以及执行的顺序,在做任何自定义工作时都是非常重要的。

有人也许会问,既然已经重新定义了自己的实现,为什么还要将其“伪装”成默认的ProfileService呢?因为这种“自定义”其实并不为“官方”所承认,这么做能够保证了兼容性,保证了第三方的组件也能使用Profile Service,即使它们没有“意识”到没有使用ASP.NET AJAX提供的默认Profile Service。


二、直接修改Sys.Services._ProfileService.prototype

Sys.Services._ProfileService完全通过prototype定义,这不光非常有利于“继承”,它的一个很大的特点就是能够通过直接修改prototype而做到修改类的行为,它对于已经实例化的对象依然有用。例如之需要如下的代码就能够为它的load函数提供一个trace功能:
Sys.Service._ProfileService.prototype._originalLoad = Sys.Service._ProfileService.prototype.load;
Sys.Service._ProfileService.prototype.load 
= function(propertyNames, loadCompletedCallback, failedCallback, userContext)
{
    
for (var i = 0; i < propertyNames.length; i++)
    {
        debug.trace(propertyNames[i]);
    }

    Sys.Service._ProfileService.prototype._originalLoad(propertyNames, loadCompletedCallback, failedCallback, userContext);
}

事实上,如果需要修改类库已有的行为,最方便的就是这种做法。从这里可以看出JavaScript的灵活性,以及使用prototype的好处。
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

16 条回复

  1. 老赵
    admin
    链接

    老赵 2006-11-05 21:33:00

    其实这个只能算是小技巧吧……

  2. 木野狐
    *.*.*.*
    链接

    木野狐 2006-11-05 21:43:00

    学习了:)

  3. 老赵
    admin
    链接

    老赵 2006-11-05 21:45:00

    @木野狐
    :)
    事实上这篇文章属于比较弱的……

  4. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-05 21:46:00

    我也正准备封装自己的ProfileService,要比原来的提供更高层次的封装,但也要有灵活的扩展性,现在先参考一下大家的做法。

  5. 老赵
    admin
    链接

    老赵 2006-11-05 21:49:00

    @Cat Chen
    你希望提供什么样的功能呢?现在ASP.NET AJAX取消了Assembly-based Web Services Access,对于需要涉及到访问Web Service方法的组件不是个好消息啊。使用和部署都会有点麻烦了。

  6. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-05 22:01:00

    @Jeffrey Zhao
    我也同意,其实将一组做好的带WebService的功能封装为一个assembly是很常用的做法,就如封装控件assembly时要把js/jpg带上一样。如果要支持assembly,就要自己另外开一个HttpHandler,然后利用Atlas原有的功能来做基本的处理,这看起来不太简洁。不过这个问题也可能眨眼之间就解决了——没有人会知道Beta2会有什么新改动,包括把这个改回来。

    因为我用Atlas就喜欢用于客户端为中心的应用,所以我的改造目标是存放任意Object。直接把JSON当作string来存看起来不错,反正我很少会在服务器端读Profile,需要时再把JSON转为我要的东西。

  7. 老赵
    admin
    链接

    老赵 2006-11-05 22:05:00

    @Cat Chen
    转化成JSON字符串然后存放在Profile中?这个相当于破坏了Profile,不太好吧……另外写一个可能不错。
    其实Profile Service的作用是在客户端使用Profile,所以不能存放任意的Object。你这个相当于提供了一个新的功能了,这样做就真的无法再服务器端使用Profile了,呵呵。

  8. 小蜗牛
    *.*.*.*
    链接

    小蜗牛 2006-11-05 22:44:00

    实用就好。

  9. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-06 10:17:00

    @Jeffrey Zhao
    在服务器端访问也可以,不过就是需要convert咯。但到底怎么做得好用,这个问题要慢慢思考。

    对于ASP.NET来说,要做一样双端都封装得很好的东西不容易,直到Atlas的出现。Atlas带来的好处是,看起来你可以做一个双端控件,两边都表现出此对象所需要具有的控件特性,虽然这还没有人做,但Extender/Behavior就已经有很多。然而我们无法摆脱的一个问题是,服务器端设计往往都以隐藏客户端细节为目标之一,这些细节隐藏而当数据到了客户端之后情况就会变得复杂。

    例子就是表面上简单的ID隐藏了ClientID这个复杂的细节。由于控件设计思想本身就是降低耦合度,每一个控件对自己本身的逻辑负责,降低控件外环境的依赖程度,所以Extender/Behavior组合能够很成功——Extender可以高度独立,同时Behavior也可以高度独立,只管它输出时指派要它负责的DomElement,其余什么都不用管。然后问题来了,如果是以客户端为中心的应用,就有可能需要和该Behavior打交道,这时候我要找到该Behavior怎么找?$find?这其实不容易,特别是当Behavior在多层NamingContainer中时。如果我去记录NamingContainer的ID在客户端用,意味着增加耦合度。

  10. 老赵
    admin
    链接

    老赵 2006-11-06 15:52:00

    @Cat Chen
    我认为,其实ExtenderContorl的重要目的就是隐藏客户端的实现和操作,而不是让开发人员在控件之外,再使用代码去操作已经在使用的Behavior,如果真要这样,则应该放出另外的接口,而不是直接操作Behavior,至少不是直接使用$find。
    换句话说“我就是不想让你在客户端操作”。当然在实现Behavior时则是另外一回事情了,我认为在这里的耦合度增加是比较正常的,例如同一个Framework中的类,如果是配合着完成同一个功能,一般耦合度会比较高。这个是能够接受的。

  11. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-09 17:35:00

    今天看完你的
    探究Application Services
    这三篇文章,深受指点,但有点遗憾的是你没有给出DEMO,我喜欢有DEMO的文章,这样看完文章,再看DEMO,再回头看文章。感觉不错

    虽然AJAX现在1。0了,不过你的这一序列文章还是蛮受用的,虽然ajax的代码在一定程度已改变了很多.

  12. 老赵
    admin
    链接

    老赵 2007-02-09 17:49:00

    @孤叶(学习.net框架)
    这部分代码,除了声明以外没有任何改变。:)

  13. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-10 09:02:00

    @Jeffrey Zhao
    嗯,我没有看过之前版本的源码,我只有看过AJAX1。0的,我是对比你放在文章中的源码,哈,所以才说已有变化,

  14. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-10 09:55:00

    问个问题,以下是ProfileService类中的一个方法,有很多类似下面这种情况我看不懂,望指点下
    private static int SetProfile(HttpContext context, IDictionary<string, object> values)
    {
    if (((values == null) || (values.get_Count() == 0)) || (ProfileService._allowedSet == null))
    {
    return 0;
    }
    ProfileBase base1 = context.get_Profile();
    //
    像values.get_Count()
    context.get_Profile()
    这些方法,本身不是.net提供的,客户端也没有相应的方法,我不知为什么可以这样用,这是怎么一回事,速回

  15. 老赵
    admin
    链接

    老赵 2007-02-11 14:01:00

    @孤叶(学习.net框架)
    有一些吧,呵呵。
    我看了一下,还是我熟悉的那些。:)

  16. 老赵
    admin
    链接

    老赵 2007-02-11 14:21:00

    @孤叶(学习.net框架)
    您一定是反编译程序集后看的吧?
    您看得set_XXX,get_XXX方法其实是属性的set方法和get方法。
    int a = values.get_Count(),其实在原来的代码中就是int a = values.Count。
    现在官方已经公布源代码了,您可以直接看它们。:)

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我