在这里,我假设你肥肥的客户端是基于MVC设计,同时Web Service的内容是以数据为中心的。
在说客户端之前,必须先说说服务器端如何设计。如果服务器端你习惯了3层设计,那就保留你以前的数据访问层和业务逻辑层设计方法,用一模一样的方法完成这两层,只不过我们不再有通过Page表现的UI,而是通过Web Service直接暴露一些业务逻辑层的调用,这就是服务器端需要完成的一切。看起来没有了Page轻松多了,是不是?因为那些工作都转嫁给客户端了。接着要开始用Atlas做我们的客户端了,在这此之前你需要读一下Atlas的各种官方/非官方文档,了解一下它的使用方法,同时读一下它的JavaScript源代码了解一下它的内部结构。
我们首先来看看客户端的Model如何实现,客户端Model应该是用来存储数据并和Web Service打交道,所以它应该设计为一个处于客户端的业务逻辑层。然而这不是一个单纯的业务逻辑层,因为真正的业务逻辑在服务器端,客户端的Model仅仅是在客户端暴露和服务器端业务逻辑层一致的调用。同时它也内涵一个小小的数据访问层,这个数据访问层服务则Web Service打交道,需要包括批量调用和数据缓存与索引等能力。现在我们的分层看起来应该是这样的:
Server Data Access Layer <-> Server Business Layer <-> Client Data Access Layer <-> Client Business Layer
我想肯定有人要问,为什么不把整个业务逻辑层搬到客户端,然后就可省去服务器端的业务逻辑层咯,让客户端的数据访问层和服务器端的数据访问层直接沟通就好了。这样做的最大问题就是,你不知道客户端提交的数据是否一定经过了客户端那个业务逻辑层的处理,用户直接提交一些不符合业务逻辑的数据到服务器端就麻烦了。
继续说我们的客户端Model需要具有的能力。例如服务器端业务层有GetProductById这个方法用于返回一个Product对象,同时客户端的Controller也需要调用此方法,那就应该在客户端的Model暴露GetProductById这个方法。最简单的办法,当然是每次客户端执行GetProductById时,都通过Web Service调用服务器端的GetProductById,然而这却是最低效率的做法。想要提高效率的话,肯定要在客户端缓存数据,这时候客户端就会缓存一些Product对象,而GetProductById方法则需要根据一定的策略决定应该优先搜索缓存还是直接访问Web Service。缓存策略是需要根据应用制定的,什么情况下应该判定缓存过期是没有通则的,这需要你自己去设计。而缓存的方式则有比较固定的做法,就是利用JavaScript特有的关联数组作索引。例如ProductId是Guid,而且使用之前说到的GuidConverter直接转换为String,那就可以创建一个Product.IdIndex对象,然后以ProductId作为关联数组的键来保存Product对象,这样就可以通过Product.IdIndex[ProductId]方法检索一个该ProductId对应的Product对象了。
Model相关的对象,例如Product类和Product.IdIndex对象,可以完全不使用任何Atlas特性,仅仅是普通JavaScript类,因为它们完全不使用Atlas的任何特性都能够好好工作。但你也可以考虑让他们继承自Sys.Component,同时按照Atlas的方式来声明其属性、方法、事件,这样他们能够获得更好的封装性,对debug.dump更好的支持,以及可能更低的执行效率。
然后轮到说View了。View可以说是最简单的部分。说它简单,是因为它和服务器端的View没什么不同,只要你懂得制作服务器端控件,你就知道如何继承Sys.UI.Control制作客户端控件,然后和在Page中一样直接往DOM扔控件就可以了,而且还不需要管SessionState和ViewState这样的东西。有问题吗?例如需要数据,或者有事情需要报告吗?请举手,通过调用或者事件告诉Controller,Controller会通过Model为你处理好这一切,View需要管的仅仅就是用户看得到摸得着的东西。
最后需要搞定的就是Controller了,这时候你有两个选择:使用JavaScript或者使用XmlScript。如果Model和View已经让你写JavaScript写到手痛,那么这时候你可以考虑使用XmlScript来写Controller。你首先把View相关的元素用HTML声明,然后在XmlScript中声明相关的控件以及它们的属性如何绑定到对应的Model对象,那就行了。但如果在Controller里面设计动态创建控件,又或者涉及复杂的数据绑定方式,XmlScript就无法满足你的需求了,这时候还是老老实实地用JavaScript写吧。
2006年7月26日星期三
2006年7月22日星期六
初次使用Atlas JavaScript (Part 2 - Web Service扩展)
Atlas对Web Service两方面的扩展包括:
1.自动生成javascript代理类的代码
2.在javascript代理类调用时使用JSON表达式进行数据交换
通过看Atlas项目的web.config,你可以发现*.asmx现在关联到了一个新的handler,也就是ScriptHandlerFactory,这个handler的行为很简单,如果请求是REST方式则用新的RestHandlerFactory来处理,否则交回给原来的WebServiceHandlerFactory处理。RestHandlerFactory也有一个选择分支,如果请求的是*.asmx/js,那就是请求客户端代理类的代码,使用RestClientProxyHandler处理,否则使用RestHandler处理,下面详细说说它们的原理:
先看看Web Service的引用,当你在ScriptManager添加一个Service引用的时候,实际添加的是到该*.asmx/js的script引用,然后就得到了代理类的代码,之后你就可以直接在javascript中使用该Web Service的类名称(包括完整的namespace)调用,参数列表则和该Web Service的参数列表一致,最后加上一个参数声明该调用的回调函数。RestClientProxyHandler需要生成如此一个代理类,就需要通过反射扫描所有的Web Services函数签名,然后将函数相关的所有类型注册到客户端。这个注册并不简单,因为Web Service有可能涉及任何类型,包括用户自定义的类型。如果是.NET Framework的一些基础类型,Atlas知道如何将它们转换为对应的javascript对应类;如果是Enum,Atlas也能够自动转换为对应的javascript代码;但遇到了不是基础的struct或者object,Atlas自身就不懂得如何将它们转换为对应的,这时候就要寻找是否有对应的JavaScriptConverter了。
JavaScriptConverter是处理服务器端和客户端之间对象串行话/并行话的基类。现在用创建一个GuidConverter的目标来举例,首先通过SupportedTypes属性声明自己支持的服务器端类型为Guid吧,再通过GetClientTypeName返回客户端类型的名称为String,这样RestClientProxyHandler就知道生成代理类时Guid类型的参数在客户端对应为String类型。
接着说说RestHandler,它处理Web Service参数的时候使用的是JSON表达式。和RestClientProxyHandler一样,对于能够处理的类型它自己能够进行串行话/并行话转换,对于不能够处理的参数它寻找对应的JavaScriptConverter帮助。
继续上面GuidConverter的例子,我们需要实现Serialize和Deserialize这两个函数,将Guid串行话为String仅需要这样:
return "\"" + ((Guid)o).ToString("D") + "\"" //o是待串行话object
而将String并行话为Guid则仅需要这样:
return (new Guid(s.Replace("\"", string.Empty))); //s是待并行话的String的表达式
最后在web.config的converter节注册一下GuidConverter,Web Service就能够正确的和客户端交换类型为Guid的数据了。
系统自带的JavaScriptConverter包括DataSetConverter、DataTableConverter、DataRowConverter,注意这3各类都没有实现Deserialize所以仅能用于将数据传送到客户端。
1.自动生成javascript代理类的代码
2.在javascript代理类调用时使用JSON表达式进行数据交换
通过看Atlas项目的web.config,你可以发现*.asmx现在关联到了一个新的handler,也就是ScriptHandlerFactory,这个handler的行为很简单,如果请求是REST方式则用新的RestHandlerFactory来处理,否则交回给原来的WebServiceHandlerFactory处理。RestHandlerFactory也有一个选择分支,如果请求的是*.asmx/js,那就是请求客户端代理类的代码,使用RestClientProxyHandler处理,否则使用RestHandler处理,下面详细说说它们的原理:
先看看Web Service的引用,当你在ScriptManager添加一个Service引用的时候,实际添加的是到该*.asmx/js的script引用,然后就得到了代理类的代码,之后你就可以直接在javascript中使用该Web Service的类名称(包括完整的namespace)调用,参数列表则和该Web Service的参数列表一致,最后加上一个参数声明该调用的回调函数。RestClientProxyHandler需要生成如此一个代理类,就需要通过反射扫描所有的Web Services函数签名,然后将函数相关的所有类型注册到客户端。这个注册并不简单,因为Web Service有可能涉及任何类型,包括用户自定义的类型。如果是.NET Framework的一些基础类型,Atlas知道如何将它们转换为对应的javascript对应类;如果是Enum,Atlas也能够自动转换为对应的javascript代码;但遇到了不是基础的struct或者object,Atlas自身就不懂得如何将它们转换为对应的,这时候就要寻找是否有对应的JavaScriptConverter了。
JavaScriptConverter是处理服务器端和客户端之间对象串行话/并行话的基类。现在用创建一个GuidConverter的目标来举例,首先通过SupportedTypes属性声明自己支持的服务器端类型为Guid吧,再通过GetClientTypeName返回客户端类型的名称为String,这样RestClientProxyHandler就知道生成代理类时Guid类型的参数在客户端对应为String类型。
接着说说RestHandler,它处理Web Service参数的时候使用的是JSON表达式。和RestClientProxyHandler一样,对于能够处理的类型它自己能够进行串行话/并行话转换,对于不能够处理的参数它寻找对应的JavaScriptConverter帮助。
继续上面GuidConverter的例子,我们需要实现Serialize和Deserialize这两个函数,将Guid串行话为String仅需要这样:
return "\"" + ((Guid)o).ToString("D") + "\"" //o是待串行话object
而将String并行话为Guid则仅需要这样:
return (new Guid(s.Replace("\"", string.Empty))); //s是待并行话的String的表达式
最后在web.config的converter节注册一下GuidConverter,Web Service就能够正确的和客户端交换类型为Guid的数据了。
系统自带的JavaScriptConverter包括DataSetConverter、DataTableConverter、DataRowConverter,注意这3各类都没有实现Deserialize所以仅能用于将数据传送到客户端。
初次使用Atlas JavaScript (Part 1 - JavaScript扩展)
Atlas的服务器端控件已经说了,现在说说客户端脚本。Atlas的脚本分为xml-script和javascript两部分:xml-script作为一种描述性语言主要用来做Controller,直接放在页面里面;javascript经过Atlas库扩展后可以用来写Atlas特有的客户端控件,而其内容最好放在独立的js文件中通过引用来使用。xml-script我还没有专门研究过,不是太懂其机制,所以现在先说说其javascript扩展。
首先,Atlas对javascript语言本身作了一定的扩展,例如让javascript支持继承,还有抽象函数、寒暑重写等概念,可以说是极力模仿C#风格。这对于习惯用C#的人来说是好事情,而且内联而非prototype方式声明外露函数和属性也确保了内部变量的不会被外部访问到,从而确保了类的封装性,但这样做却让Altas的执行效率显得比其它framework要差一些。另外Atlas还对javascript本身的String、Array、Date、Number作了简单的扩展,目的是为了模仿.NET类库对应的类的常用方法,方便javascript编写者。关于Atlas对javascript的扩展,详细可以参考这篇ASP.NET Atlas对JavaScript的扩展。(由于Atlas还在不断改进中,无论是官访文档还是自己研究Atlas源代码者写的文档都仅仅是关注到作者认为应该关注的方面,所以资料不一定全面,而且还不一定适用于日后的版本,所以最好还是使用时参考Atlas的源代码。)
当然,Atlas不可能全面扩展javascript的对象让他们拥有所有服务器端对应对象的常用方法,这时候你就需要自己扩展了。例如Date对象没有AddYears等方法,而你的程序需要这方面的Date对象操作便利,你就可以模仿着服务器端的方法去写对应的方法。模仿的最简单办法就是用Reflector打开.NET Framework看,看看它是怎样实现的,然后你改用javascript实现。由于.NET Framework系统类的实现方法效率不可能太低,所以按照它的方法去做效率应该也不会差,不过你自己有更高效的方法就更好了。
首先,Atlas对javascript语言本身作了一定的扩展,例如让javascript支持继承,还有抽象函数、寒暑重写等概念,可以说是极力模仿C#风格。这对于习惯用C#的人来说是好事情,而且内联而非prototype方式声明外露函数和属性也确保了内部变量的不会被外部访问到,从而确保了类的封装性,但这样做却让Altas的执行效率显得比其它framework要差一些。另外Atlas还对javascript本身的String、Array、Date、Number作了简单的扩展,目的是为了模仿.NET类库对应的类的常用方法,方便javascript编写者。关于Atlas对javascript的扩展,详细可以参考这篇ASP.NET Atlas对JavaScript的扩展。(由于Atlas还在不断改进中,无论是官访文档还是自己研究Atlas源代码者写的文档都仅仅是关注到作者认为应该关注的方面,所以资料不一定全面,而且还不一定适用于日后的版本,所以最好还是使用时参考Atlas的源代码。)
当然,Atlas不可能全面扩展javascript的对象让他们拥有所有服务器端对应对象的常用方法,这时候你就需要自己扩展了。例如Date对象没有AddYears等方法,而你的程序需要这方面的Date对象操作便利,你就可以模仿着服务器端的方法去写对应的方法。模仿的最简单办法就是用Reflector打开.NET Framework看,看看它是怎样实现的,然后你改用javascript实现。由于.NET Framework系统类的实现方法效率不可能太低,所以按照它的方法去做效率应该也不会差,不过你自己有更高效的方法就更好了。
2006年7月19日星期三
Atlas 的服务器端控件 - 易用但不灵活
首先,我会把Atlas服务器端控件区分为两类,纯服务器端控件和客户端逻辑封装控件。前者类似WebControl派生出来的控件那样,它自身并非直接呈现(render)一个客户端元素的实例就算,而是拥有复杂的服务器端逻辑,它呈现出来的HTML由交错的规则决定着;至于后者,则类似HtmlControl派生出来的控件,它所做的基本上就是将自身呈现为一个单一的客户端元素,其服务器端属性比较直接的生成客户端元素的属性或者子元素,不过客户端逻辑封装控件输出的不是HTML,而是Atlas特有的xml-script。
纯服务器端控件是Atlas服务器端的重点,这些控件在Microsoft.Web.UI而不在Microsoft.Web.UI.Controls,由此也可以看出他们的核心地位,它们是ScriptMananger(ScriptManagerProxy)和UpdatePanel。
首先说说ScriptManager,它都是一个非常有用的控件,扔一个ScriptManager到Page上面,它就会自动帮你生成引用Atlas核心js的引用,这样Atlas函数库就能够被使用,xml-script也能被解释。对于一个ScriptManager,你可以向它添加Script和Service引用。
可添加Script引用的包括Atlas内置的Script模块(AtlasUIDragDrop、AtlasUIGlitz、AtlasUIMap)和是你自己的js文件,如果添加的是js文件你可以使用"~"路径运算符以确保无论在什么路径的页面都能正确引用这个js文件。
添加Service引用的Service是ASP.NET Web Service,添加引用后你可以直接在JavaScript或xml-script中调用该Web Service而无须管后台是怎么实现的。另外,ScriptManager还有一个重要的属性,就是EnablePartialRendering,启用后ScriptManager就会去分辨一个PostBack是正常提交的PostBack还是XMLHttpRequest发回来的异步PostBack,如果是后者它就会优化处理,详细下面再解释。
然后说说UpdatePanel,这就是Atlas让你最容易开发一个“看起来很Ajax”的应用的地方。所谓的UpdatePanel,其实就是圈定一个区域,里面的控件或者外部注册为异步调用的控件触发PostBack时并不真正提交页面而是用XMLHttpRequest发送异步PostBack,之后分析从服务器端返回的HTML并更新这个区域内的HTML代码。你可以设置一个UpdatePanel不是每次都需要更新内容,当一个异步PostBack发生时就根据一堆的Trigger设置来判断一个UpdatePanel是否在本次异步PostBack时需要更新,同时ScriptManager开启了EnablePartialRendering的话则只有需要更新的区域内的HTML才会被发送到客户端,这样就可以节省沟通成本。
虽然这样看起来UpdatePanel非常灵活,区域有多大以及什么条件下才更新都能够设置,一眼看起来设置适当的话非常好用。但其实并不存在“设置适当”,UpdatePanel并不是万能,它不能像普通控件那里放在任何区域之内、包容任何控件、在页面生命周期的任何阶段声明,所以不是你喜欢怎样放就怎样放。而且当UpdatePanel多起来Trigger也不容易设置完美,总会导致应该更新而没更新的情况,而那个时候你就会想还是把所有UpdatePanel设置为总是更新算了。
UpdatePanel最坏的地方我认为是破坏对象结构。服务器端控件和客户端的DOM都是树状结构,目标就是保持逻辑的局部性,所以Response.Write和Document.Write这种破坏局部性的操作方式我觉得都不应该被使用,而UpdatePanel区域的刷新就如Response.Write一样,一刷就是整个区域。虽然在服务器端区域内的还是控件,还是可以按控件进行逻辑处理,但是在客户去这样一刷,DOM元素都被破坏掉了,区域内增加删除修改了哪些DOM元素完全没办法知道,这时候该区域内外的View和Model都独立,要建立一个跨越区域边界的Controller也不容易,当内部的View和Modal被整个替换掉的时候,Controller完全不知道变更的细节,而需要自己去适应新送上来的View和Model。对此暂时我觉得可行的解决方案就是建立一个内部的Controller,让该Contoller负责和外部Contoller的沟通,而该Controller由服务器端生成,所以它可以知道新旧的View和Model之间的变更细节。
简单来说,用UpdatePanel就如用不闪烁的IFrame一样,传统的做法能够马上用上,但效率不高,灵活性也低,最后你会发现不如整个页面都放在一个大的UpdatePanel里面,让它“看起来不闪烁”就算了。
纯服务器端控件是Atlas服务器端的重点,这些控件在Microsoft.Web.UI而不在Microsoft.Web.UI.Controls,由此也可以看出他们的核心地位,它们是ScriptMananger(ScriptManagerProxy)和UpdatePanel。
首先说说ScriptManager,它都是一个非常有用的控件,扔一个ScriptManager到Page上面,它就会自动帮你生成引用Atlas核心js的引用,这样Atlas函数库就能够被使用,xml-script也能被解释。对于一个ScriptManager,你可以向它添加Script和Service引用。
可添加Script引用的包括Atlas内置的Script模块(AtlasUIDragDrop、AtlasUIGlitz、AtlasUIMap)和是你自己的js文件,如果添加的是js文件你可以使用"~"路径运算符以确保无论在什么路径的页面都能正确引用这个js文件。
添加Service引用的Service是ASP.NET Web Service,添加引用后你可以直接在JavaScript或xml-script中调用该Web Service而无须管后台是怎么实现的。另外,ScriptManager还有一个重要的属性,就是EnablePartialRendering,启用后ScriptManager就会去分辨一个PostBack是正常提交的PostBack还是XMLHttpRequest发回来的异步PostBack,如果是后者它就会优化处理,详细下面再解释。
然后说说UpdatePanel,这就是Atlas让你最容易开发一个“看起来很Ajax”的应用的地方。所谓的UpdatePanel,其实就是圈定一个区域,里面的控件或者外部注册为异步调用的控件触发PostBack时并不真正提交页面而是用XMLHttpRequest发送异步PostBack,之后分析从服务器端返回的HTML并更新这个区域内的HTML代码。你可以设置一个UpdatePanel不是每次都需要更新内容,当一个异步PostBack发生时就根据一堆的Trigger设置来判断一个UpdatePanel是否在本次异步PostBack时需要更新,同时ScriptManager开启了EnablePartialRendering的话则只有需要更新的区域内的HTML才会被发送到客户端,这样就可以节省沟通成本。
虽然这样看起来UpdatePanel非常灵活,区域有多大以及什么条件下才更新都能够设置,一眼看起来设置适当的话非常好用。但其实并不存在“设置适当”,UpdatePanel并不是万能,它不能像普通控件那里放在任何区域之内、包容任何控件、在页面生命周期的任何阶段声明,所以不是你喜欢怎样放就怎样放。而且当UpdatePanel多起来Trigger也不容易设置完美,总会导致应该更新而没更新的情况,而那个时候你就会想还是把所有UpdatePanel设置为总是更新算了。
UpdatePanel最坏的地方我认为是破坏对象结构。服务器端控件和客户端的DOM都是树状结构,目标就是保持逻辑的局部性,所以Response.Write和Document.Write这种破坏局部性的操作方式我觉得都不应该被使用,而UpdatePanel区域的刷新就如Response.Write一样,一刷就是整个区域。虽然在服务器端区域内的还是控件,还是可以按控件进行逻辑处理,但是在客户去这样一刷,DOM元素都被破坏掉了,区域内增加删除修改了哪些DOM元素完全没办法知道,这时候该区域内外的View和Model都独立,要建立一个跨越区域边界的Controller也不容易,当内部的View和Modal被整个替换掉的时候,Controller完全不知道变更的细节,而需要自己去适应新送上来的View和Model。对此暂时我觉得可行的解决方案就是建立一个内部的Controller,让该Contoller负责和外部Contoller的沟通,而该Controller由服务器端生成,所以它可以知道新旧的View和Model之间的变更细节。
简单来说,用UpdatePanel就如用不闪烁的IFrame一样,传统的做法能够马上用上,但效率不高,灵活性也低,最后你会发现不如整个页面都放在一个大的UpdatePanel里面,让它“看起来不闪烁”就算了。
2006年7月7日星期五
ViewState - ASP.NET 的一个特有存储容器
首先,我不确定是不是只有ASP.NET由ViewState,也不确认它有多特有,只是觉得这个东西对于Web开发MVC分离的进步很有帮助。
所谓的ViewState,就是用来存放关于View的State的地方。以前的存储容器包括Cookies, Session, Application, Cache, Hidden,有时候连传递变量用的QueryString也用作存储容器,但都不是专门用来存储View相关信息的地方,然而由于没有专门存放View相关信息的地方,所以人们只好乱放。不怕过期失效的变量,多数人会选择放在Session里,而且跨页面不会丢失,用户访问几个别的页面回来还能通过Session恢复本页的View。如果需要延长一些时间,而数据又不是很多的话,可以放Cookies,和Session类似。而数据真的很短,而且页面总是提交给自己的情况下,用QueryString作为一些跨页面生命周期的变量的保存方式也可以。而如果页面可以只用Post不用Get传递的话,那么Hidden也是一个很好的选择,因为Hidden容量大,不在地址留下信息。在ASP.NET当中,就是设计到大多数情况都是Post(除了直接链接),所以用Hidden存放和View相关的信息是非常适合的。
既然用Hidden存放就可以了,为什么需要ViewState这样的统一管理呢?最显然的理由就是加密。ViewState不是给用户看或者修改的信息,仅仅是因为View的状态会在页面生命周期之间丢失,所以我们要将这些信息输出到HTML再等Post的时候取回来,对ViewState加密(至少校验)能够确保View正确无误的恢复。ASP.NET内置的ViewState可以方便的设置校验和加密,只要你把可以序列化的对象存进去,它就能够自动序列化并输出到HTML,同时该对象的保存与恢复是跟控件名以及在控件树中的位置相关的,就如ASP.NET控件的其他特性一样,确保了树中不同位置的同ID控件的数据不会被混淆。简而言之,ViewState是保存跨页面生命周期有关变量的最好容器,但它又不能够跨出页面范围称为会话变量的容器(那应该是Session负责的哦),所以是真正符合其名称用来保存View的State的。
有很多ASP.NET的新手不知道ViewState的用途,认为它是ASP.NET的内部对象,平时还是仅用ASP那套公开的对象好了,那就会带来很多麻烦。例如有GridA和GridB两个页面,点击一个条目查看明细都回转到Details页面,同时Details页面要提供返回原来页面的途径(包括原本GridView所浏览到的分页)。如何存储原本页面的状态呢,包括它来自GridA还是GridB以及原来的GridView所在的分页?有人选择用QueryString传递给Details页面,让Details页面构造返回链接时再通过QueryString把状态传回去。显然,原来页面的状态不是对Details的Query(查询),那么用QueryString传递给Details页面是不合适的。也有人选择用Session传递,但是这个状态仅仅做一次传递,难道我对你说一句话也算是Session(会话)?当然不算。用Hidden是一个方法,但是基于我上面所说的ViewState对Hidden的改良,在这种情况下就应该用ViewState作为原本GridA或GirdB的View状态的保存方式,并且在经过Details页面返回之后再还原。需要说明的是,只有ASP.NET 2.0才在内部支持跨页面PostBack,ASP.NET 1.x不支持跨页面PostBack,也无法跨页面接受和保持ViewState。
最后就是使用ViewState需要注意的地方。过量使用ViewState当然有损效率,但这对于网站的数据正确性来说还不至于造成漏洞,真正会造成漏洞的就是ViewState重名。上面不是说了ViewState是根据控件树位置和控件ID保存所以不会重名的吗?在树是静态的情况下确实是这样,但是如果树是动态创建的,而ID则是可能在不同的语义下重复使用的,那就可能破坏数据的正确性了。例如一个DataControl的子控件自动命名为Control_1, Control_2, Control_3等,如果在PostBack后该DataControl被再次DataBind,那么DataSource可能已经改变了,所以需要清除所有的子控件并重新根据DataSource创建它们,此时别忘记了调用ClearChildState方法把子控件的ViewState和ControlState都清除掉,否则当你重新创建子控件而它们的名称还是Control_1, Control_2, Control_3等的时候,就可能会把原来的ViewState加载回去。
所谓的ViewState,就是用来存放关于View的State的地方。以前的存储容器包括Cookies, Session, Application, Cache, Hidden,有时候连传递变量用的QueryString也用作存储容器,但都不是专门用来存储View相关信息的地方,然而由于没有专门存放View相关信息的地方,所以人们只好乱放。不怕过期失效的变量,多数人会选择放在Session里,而且跨页面不会丢失,用户访问几个别的页面回来还能通过Session恢复本页的View。如果需要延长一些时间,而数据又不是很多的话,可以放Cookies,和Session类似。而数据真的很短,而且页面总是提交给自己的情况下,用QueryString作为一些跨页面生命周期的变量的保存方式也可以。而如果页面可以只用Post不用Get传递的话,那么Hidden也是一个很好的选择,因为Hidden容量大,不在地址留下信息。在ASP.NET当中,就是设计到大多数情况都是Post(除了直接链接),所以用Hidden存放和View相关的信息是非常适合的。
既然用Hidden存放就可以了,为什么需要ViewState这样的统一管理呢?最显然的理由就是加密。ViewState不是给用户看或者修改的信息,仅仅是因为View的状态会在页面生命周期之间丢失,所以我们要将这些信息输出到HTML再等Post的时候取回来,对ViewState加密(至少校验)能够确保View正确无误的恢复。ASP.NET内置的ViewState可以方便的设置校验和加密,只要你把可以序列化的对象存进去,它就能够自动序列化并输出到HTML,同时该对象的保存与恢复是跟控件名以及在控件树中的位置相关的,就如ASP.NET控件的其他特性一样,确保了树中不同位置的同ID控件的数据不会被混淆。简而言之,ViewState是保存跨页面生命周期有关变量的最好容器,但它又不能够跨出页面范围称为会话变量的容器(那应该是Session负责的哦),所以是真正符合其名称用来保存View的State的。
有很多ASP.NET的新手不知道ViewState的用途,认为它是ASP.NET的内部对象,平时还是仅用ASP那套公开的对象好了,那就会带来很多麻烦。例如有GridA和GridB两个页面,点击一个条目查看明细都回转到Details页面,同时Details页面要提供返回原来页面的途径(包括原本GridView所浏览到的分页)。如何存储原本页面的状态呢,包括它来自GridA还是GridB以及原来的GridView所在的分页?有人选择用QueryString传递给Details页面,让Details页面构造返回链接时再通过QueryString把状态传回去。显然,原来页面的状态不是对Details的Query(查询),那么用QueryString传递给Details页面是不合适的。也有人选择用Session传递,但是这个状态仅仅做一次传递,难道我对你说一句话也算是Session(会话)?当然不算。用Hidden是一个方法,但是基于我上面所说的ViewState对Hidden的改良,在这种情况下就应该用ViewState作为原本GridA或GirdB的View状态的保存方式,并且在经过Details页面返回之后再还原。需要说明的是,只有ASP.NET 2.0才在内部支持跨页面PostBack,ASP.NET 1.x不支持跨页面PostBack,也无法跨页面接受和保持ViewState。
最后就是使用ViewState需要注意的地方。过量使用ViewState当然有损效率,但这对于网站的数据正确性来说还不至于造成漏洞,真正会造成漏洞的就是ViewState重名。上面不是说了ViewState是根据控件树位置和控件ID保存所以不会重名的吗?在树是静态的情况下确实是这样,但是如果树是动态创建的,而ID则是可能在不同的语义下重复使用的,那就可能破坏数据的正确性了。例如一个DataControl的子控件自动命名为Control_1, Control_2, Control_3等,如果在PostBack后该DataControl被再次DataBind,那么DataSource可能已经改变了,所以需要清除所有的子控件并重新根据DataSource创建它们,此时别忘记了调用ClearChildState方法把子控件的ViewState和ControlState都清除掉,否则当你重新创建子控件而它们的名称还是Control_1, Control_2, Control_3等的时候,就可能会把原来的ViewState加载回去。
2006年7月1日星期六
OpenID - 继claimID之后
首页在这里:openid.net
OpenID的目的是一个真正的分布式Identity系统,可以用于验证你是某URL的Owner。在范例中,它假设你有一个Blog,然后你在另外一个支持OpenID的Blog发表评论,在评论后你就可以输入你的OpenID登录。所谓你的OpenID,就是一个URL,例如你的Blog的URL。在收到评论提交和OpenID后,该Blog系统就会去你的URL检索OpenID信息,并到指定的OpenID服务器验证你的登录,验证成功就确认你是来自该URL的用户。
如果用过Google Sitemaps,你就知道要查看该站点的统计信息必须先确认你是该站点的Owner,而方法就是Google会要求你对该站点的首页插入一个指定meta标记,然后Google检测到该标记后就确认你是该站点的Owner了。Google Sitemaps是由Google统一确认你的Owner身份,而OpenID则允许你在你的页面中通过<link rel="openid.server">指向任何一个OpenID验证服务器,这就是它所谓分布式的地方。至于指向的Server通过何种方法确认你是该URL的Owner则是不确定的,例如如果该Server就是你的BSP(Blog Service Provider),那么你提供你的Blog的URL和密码它验证,这是很简单的事情。
OpenID的目的是一个真正的分布式Identity系统,可以用于验证你是某URL的Owner。在范例中,它假设你有一个Blog,然后你在另外一个支持OpenID的Blog发表评论,在评论后你就可以输入你的OpenID登录。所谓你的OpenID,就是一个URL,例如你的Blog的URL。在收到评论提交和OpenID后,该Blog系统就会去你的URL检索OpenID信息,并到指定的OpenID服务器验证你的登录,验证成功就确认你是来自该URL的用户。
如果用过Google Sitemaps,你就知道要查看该站点的统计信息必须先确认你是该站点的Owner,而方法就是Google会要求你对该站点的首页插入一个指定meta标记,然后Google检测到该标记后就确认你是该站点的Owner了。Google Sitemaps是由Google统一确认你的Owner身份,而OpenID则允许你在你的页面中通过<link rel="openid.server">指向任何一个OpenID验证服务器,这就是它所谓分布式的地方。至于指向的Server通过何种方法确认你是该URL的Owner则是不确定的,例如如果该Server就是你的BSP(Blog Service Provider),那么你提供你的Blog的URL和密码它验证,这是很简单的事情。
2006年6月27日星期二
ASP.NET 设计优秀之处
我觉得ASP.NET设计得最好的地方,就是控件树的概念。虽然WebForm的概念很烂,误导了不少没有Web开发经验的程序员,但它带来的控件这一概念及控件树结构就真的很好。
控件树优秀的地方,就在于它把整个页面的处理逻辑分割到控件,控件与控件之间是松耦合的,不存在任何依存关系。以往的Web开发,所有的代码都是平板式的接受Request平板式的输出Response,Request被看作由不同的Request属性组成,Response被看作不同的Response片断,而这些由平板式的代码混合在一起处理,导致Request属性和Response片断之间的耦合度很大。而在ASP.NET中,一个控件就只取它需要的Request属性,然后对它输出的Response片断负责,那样就相当于解耦了。
如果两个控件之间存在一定的逻辑,这些逻辑就应该交给一个可信赖的第三方统一管理。一般情况下,这个第三方是父控件,因为父控件有权统一管理子控件的行为,所以子控件之间的交互逻辑也可以交由父控件处理。另一种方法是通过专门的控件负责,例如WebPart之间的逻辑(WebPartConnection)统一由ConnectionsZone管理。控件间逻辑集中在一个地方,统一管理,需要时统一根据涉及控件的存在性判断丢弃该部分逻辑或者抛出异常,这还算是一定程度上的松耦合度。
有很多ASP.NET的新手,特别是有ASP经验的,一上来就喜欢用Response.Write输出一些无法用控件输出的信息,其实这是不对的,任何一类信息都应该由专门负责的对象负责输出,如果不存在该类对象你就要自己制作一个负责此类逻辑的类。用Response.Write输出debug信息是常见错误,实际上应该开启Trace然后用Trace.Write或者Trace.Warn来输出debug信息。用Response.Write输出script也是很多人在用的方法,实际上应该用Page.ClientScript下面的RegisterClientScriptBlock等专用script注册函数。至于输出的是HTML的话,则绝对不应该用Response.Write了,如果没有任何控件能实现你要的输出逻辑,就自己写一个,按照自己的逻辑写Render,Render就是专门给你书写自己的局部HTML输出逻辑的地方,而且在Render中你最好用HtmlTextWriter的特有方法来输出HTML,因为那样做能够比较容易将Render的代码迁移到别的Mark-up Language输出环境。
控件树优秀的地方,就在于它把整个页面的处理逻辑分割到控件,控件与控件之间是松耦合的,不存在任何依存关系。以往的Web开发,所有的代码都是平板式的接受Request平板式的输出Response,Request被看作由不同的Request属性组成,Response被看作不同的Response片断,而这些由平板式的代码混合在一起处理,导致Request属性和Response片断之间的耦合度很大。而在ASP.NET中,一个控件就只取它需要的Request属性,然后对它输出的Response片断负责,那样就相当于解耦了。
如果两个控件之间存在一定的逻辑,这些逻辑就应该交给一个可信赖的第三方统一管理。一般情况下,这个第三方是父控件,因为父控件有权统一管理子控件的行为,所以子控件之间的交互逻辑也可以交由父控件处理。另一种方法是通过专门的控件负责,例如WebPart之间的逻辑(WebPartConnection)统一由ConnectionsZone管理。控件间逻辑集中在一个地方,统一管理,需要时统一根据涉及控件的存在性判断丢弃该部分逻辑或者抛出异常,这还算是一定程度上的松耦合度。
有很多ASP.NET的新手,特别是有ASP经验的,一上来就喜欢用Response.Write输出一些无法用控件输出的信息,其实这是不对的,任何一类信息都应该由专门负责的对象负责输出,如果不存在该类对象你就要自己制作一个负责此类逻辑的类。用Response.Write输出debug信息是常见错误,实际上应该开启Trace然后用Trace.Write或者Trace.Warn来输出debug信息。用Response.Write输出script也是很多人在用的方法,实际上应该用Page.ClientScript下面的RegisterClientScriptBlock等专用script注册函数。至于输出的是HTML的话,则绝对不应该用Response.Write了,如果没有任何控件能实现你要的输出逻辑,就自己写一个,按照自己的逻辑写Render,Render就是专门给你书写自己的局部HTML输出逻辑的地方,而且在Render中你最好用HtmlTextWriter的特有方法来输出HTML,因为那样做能够比较容易将Render的代码迁移到别的Mark-up Language输出环境。
2006年6月20日星期二
学习应该追求“游刃有余”的效果
“游刃有余”这个词是从哪里听来的呢?是某年GDKOI听郭嵩山(中大搞ACM兼负责广东省OI的那位)说的。他当时要表达的意思是,中大不会保送仅仅是竞赛好的学生,你必须平时学习也好才行,因此他说必须平时学习游刃有余才好搞竞赛,否则还是专心学习好。当时我就觉得这句话应该将因果倒置一下才更真实反映现实,那就是你平时学习保持在游刃有余的水平就够了,这样你才能够在竞赛方面有所发挥。
后来见到Coo的del.icio.us收藏了一篇文章,标题大概是“让一个人失去创意的最好方法就是让他超负载工作”。然后最近ziyan又说,Benny对着教学处主任说“想发展兴趣的学生是不能参加高考的”,于是我决定写一下这个题材。
“参加高考”指什么?当然不是指高考这几天的考试。我也去高考了,但对我来说无非是玩下,成绩也不入档案,更加不存在复习备考,所以不算“参加高考”。“参加高考”指的是由于高考而由高考那天此向前延伸1年的全年备考,甚至再向前延伸0至2年的部分自由的剥夺期,而这1至3年的时间里,你就处于那种“超负载工作”的状态下,所以“参加高考”实质上指的应该是至少1年的“超负载工作”。
然后有人会说,“不是啊,我高三很轻松很堕落啊”。这时候我觉得就有必要将“让一个人失去创意的最好方法就是让他超负载工作”这句话向广义推进一下,让主语变为“一群人”。由于你所处的群体处于“超负载工作”中,而创意往往是需要通过讨论、阅读等途径激发灵感,正如一个处于低温物体中的粒子一样,即使它有一个时刻处于高能状态,它也只能将能量散发出去,而不能长期处于高能状态。
如果一个学生能够让其学习保持在“游刃有余”的状态,无论他学习怎样,他总能够将他剩余的能量发挥到创意上面去,这才叫做“发展兴趣”。至于“发展兴趣”好不好,这就要看个人了。有些人天生喜欢跟大流,那是没得说的。有些人“发展兴趣”结果更好了,自然有些人结果更差了,这属于正常现象,否则所有人都去“发展兴趣”了。Benny说过,如果高考是独木桥的话,竞赛就是峭壁——很宽很宽问题只是你是否爬得上。
最后,就是必须区分“游刃有余”和“好吃懒做”。“游刃有余”并不能成为“好吃懒做”的借口,否则你将剩余力量都浪费掉了结果就当然比“超负载工作”要差。
后来见到Coo的del.icio.us收藏了一篇文章,标题大概是“让一个人失去创意的最好方法就是让他超负载工作”。然后最近ziyan又说,Benny对着教学处主任说“想发展兴趣的学生是不能参加高考的”,于是我决定写一下这个题材。
“参加高考”指什么?当然不是指高考这几天的考试。我也去高考了,但对我来说无非是玩下,成绩也不入档案,更加不存在复习备考,所以不算“参加高考”。“参加高考”指的是由于高考而由高考那天此向前延伸1年的全年备考,甚至再向前延伸0至2年的部分自由的剥夺期,而这1至3年的时间里,你就处于那种“超负载工作”的状态下,所以“参加高考”实质上指的应该是至少1年的“超负载工作”。
然后有人会说,“不是啊,我高三很轻松很堕落啊”。这时候我觉得就有必要将“让一个人失去创意的最好方法就是让他超负载工作”这句话向广义推进一下,让主语变为“一群人”。由于你所处的群体处于“超负载工作”中,而创意往往是需要通过讨论、阅读等途径激发灵感,正如一个处于低温物体中的粒子一样,即使它有一个时刻处于高能状态,它也只能将能量散发出去,而不能长期处于高能状态。
如果一个学生能够让其学习保持在“游刃有余”的状态,无论他学习怎样,他总能够将他剩余的能量发挥到创意上面去,这才叫做“发展兴趣”。至于“发展兴趣”好不好,这就要看个人了。有些人天生喜欢跟大流,那是没得说的。有些人“发展兴趣”结果更好了,自然有些人结果更差了,这属于正常现象,否则所有人都去“发展兴趣”了。Benny说过,如果高考是独木桥的话,竞赛就是峭壁——很宽很宽问题只是你是否爬得上。
最后,就是必须区分“游刃有余”和“好吃懒做”。“游刃有余”并不能成为“好吃懒做”的借口,否则你将剩余力量都浪费掉了结果就当然比“超负载工作”要差。
2006年6月7日星期三
为什么 BBS 在中国那么火 (Part 2 - 社区生态学)
好了,抛掉那些有暴力没美学的沙子,继续挖掘论坛的金矿。社区研究之BBS的死亡鉴定里面的理论是"火了-水了-完了",感觉对于大多数公开论坛都正确吧,但其实这里讨论的都是仅做内容服务的论坛,人们来论坛的唯一目的就是论坛里的内容,所以必须以内容养内容。但是对于不用以内容养内容的论坛来说,例如提供下载服务的TLF,因为人们是为了下载而来的,所以即使内容不怎么样也会有相当的流量,流量多了自然有人发言,发言多了既然有精华,然而即使水了也不会完了,因为流量仍在,水多水少好像一个生态环境那样自然平衡,而下载服务就好像绿色植物那样维持着食物链的存在性。
还有一些看起来不完全是以内容养内容的论坛,但又没有绿色植物的,例如一些下载交换论坛。所谓交换,就是下载不是由官方提供,而是靠大家上传,如果人都走了同样论坛会死掉。不过这样的论坛有一个好处,就是文字内容不是价值的主要来源,价值的主要来源是上传,这时候哪些内容有价值哪些内容没有价值是非常容易区分的,也就一定程度上避免的水化这个过程。
讲到内容价值,就不得不讲精华贴这个设计。这个设计是用于奖励发贴者的吗?初衷可能是,但结果绝对不是,精华贴的作用是为了浏览者尽快找到他们要找的东西,即使他们在无聊闲逛也尽快为他们提供有新鲜的有价值的事物让他们结束无聊阶段。看看TLF,已经不仅仅是有精华贴了,而是贴子分等级了,普通贴子普通颜色,不同类型的"公务贴"用不同颜色的粗体显示,让你一找就找到你想要的东西。例如在字幕区吧,发布字幕的贴子为绿色,而求字幕或者讨论字幕的贴子则为普通的黑色,这样找字幕的人一眼就能够找到字幕。即使你通过电影名称搜索字幕吧,搜索结果里也会用绿色把发布字幕那一帖显眼的显示出来,当然还可能看到蓝色的显眼贴,那是测评。有些人说论坛社区是按主题聚合,Blog社区是按人聚合,什么聚合有什么特点,但现实是——只有你想不到的聚合形式,没有实现不了的聚合形式。TLF那样把不同版面同一类型的公务贴同一颜色标记,也算是一种对内容的聚合形式,不是吗?
回到生态学的问题上,既然说到了论坛社区和Blog社区,那么就开始用社区这个词而放弃论坛这个词。越复杂的生态系统就越难建立,但建立后就越能够抵抗外力,如果一个社区构成部分相对复杂一些,例如包括论坛、电台、下载、测评,那么即使一方面受到外力的打击但仍能维持流量,要恢复并不难。社区上各式各样的用户,就如各式各样的生物,有产出有消耗,但只要一个社区能够维持食物链的稳定性,那就能够幸存下去。然而很不幸,数量最多的是那些食物链顶端的终极消费者,也就是那些仅消费不贡献的(例如长年潜水的),反而最少的是绿色植物,也就是那些需求低了与无偿贡献的。由此看来,如果单单由用户构成一个封闭的生态系统,基本上是死定的,必须依赖于与外部的交换才可能让这个生态系统运作下去。如何算是和外部交换?例如用户可以选择付费,然后这些费用最终转变成一些收费信息的来源。
还有一些看起来不完全是以内容养内容的论坛,但又没有绿色植物的,例如一些下载交换论坛。所谓交换,就是下载不是由官方提供,而是靠大家上传,如果人都走了同样论坛会死掉。不过这样的论坛有一个好处,就是文字内容不是价值的主要来源,价值的主要来源是上传,这时候哪些内容有价值哪些内容没有价值是非常容易区分的,也就一定程度上避免的水化这个过程。
讲到内容价值,就不得不讲精华贴这个设计。这个设计是用于奖励发贴者的吗?初衷可能是,但结果绝对不是,精华贴的作用是为了浏览者尽快找到他们要找的东西,即使他们在无聊闲逛也尽快为他们提供有新鲜的有价值的事物让他们结束无聊阶段。看看TLF,已经不仅仅是有精华贴了,而是贴子分等级了,普通贴子普通颜色,不同类型的"公务贴"用不同颜色的粗体显示,让你一找就找到你想要的东西。例如在字幕区吧,发布字幕的贴子为绿色,而求字幕或者讨论字幕的贴子则为普通的黑色,这样找字幕的人一眼就能够找到字幕。即使你通过电影名称搜索字幕吧,搜索结果里也会用绿色把发布字幕那一帖显眼的显示出来,当然还可能看到蓝色的显眼贴,那是测评。有些人说论坛社区是按主题聚合,Blog社区是按人聚合,什么聚合有什么特点,但现实是——只有你想不到的聚合形式,没有实现不了的聚合形式。TLF那样把不同版面同一类型的公务贴同一颜色标记,也算是一种对内容的聚合形式,不是吗?
回到生态学的问题上,既然说到了论坛社区和Blog社区,那么就开始用社区这个词而放弃论坛这个词。越复杂的生态系统就越难建立,但建立后就越能够抵抗外力,如果一个社区构成部分相对复杂一些,例如包括论坛、电台、下载、测评,那么即使一方面受到外力的打击但仍能维持流量,要恢复并不难。社区上各式各样的用户,就如各式各样的生物,有产出有消耗,但只要一个社区能够维持食物链的稳定性,那就能够幸存下去。然而很不幸,数量最多的是那些食物链顶端的终极消费者,也就是那些仅消费不贡献的(例如长年潜水的),反而最少的是绿色植物,也就是那些需求低了与无偿贡献的。由此看来,如果单单由用户构成一个封闭的生态系统,基本上是死定的,必须依赖于与外部的交换才可能让这个生态系统运作下去。如何算是和外部交换?例如用户可以选择付费,然后这些费用最终转变成一些收费信息的来源。
为什么 BBS 在中国那么火 (Part 1 - 整理及评论)
王冉的原贴在这里:为什么BBS在中国那么火,这只是一个起头,但它提到了一个"先进性"例子:"在国外(至少是在美国),虽然也有一些所谓的论坛,但是往往都是围绕一些窄众关心的具体话题,譬如某个学术问题、某部电影或者某款新电脑",这用于下文评论douban。
keso的东拉西扯:为什么BBS在中国那么火,我觉得既然要提早期Internet,就必须把概念分开来——BBS和Forum是两样不同的东西。虽然他们的很多特性看起来都一样,甚至使用起来也完全一样,但基于历史的原因我还是习惯把它们分开。BBS是Internet早期的产物,那时候还没有Web,消息都来自BBS,在当时的情况来说,BBS就是Internet的绝大部分,而后来的Forum则仅仅是Web的一种使用方式。BBS有一种特性,就是不可替代性,在那个时候你要获取信息就要通过BBS,你不能选择不用BBS或者用其他形式。从名字来看,BBS是电子公告版,而不是论坛,这就让BBS看起来有点官方色彩("官方"在这里指理想中的人民自愿放弃其部分政治权利而授权形成的政府)。至于为什么BBS后来在中国习惯被翻译为论坛,这可能就是问题的根源了,也就是论坛特性加上民族特性所造成的结果。
麦田在社区(1):聊天室启示录中,提到了论坛的技术零成本和言论零成本特性。技术零成本这没什么好说的,任何一个易用的产品都必须具有这样的特性。言论零成本,才是真正和民族特性结合的地方。因为中国人习惯了隐藏自己的欲望,特别是那些显得有点“坏”的欲望,然后又不同于菩提树下的顿悟——“我选择没有欲望,因此没有欲望满足不了的痛苦”,中国人会选择在适当的场合释放这些欲望,而且有可能是加倍的或者以变态的形式释放这些欲望。于是论坛就成了这样一个场所,和五谷轮回之所一样。“你有压力,我有压力”,“未解决”?那就去释放一下咯,只不过前者用于释放言论的压力,后者用于释放膀胱的压力。
在中文BBS:红旗下的蛋中,麦田还说到了“网络暴力”的问题,他总结网络暴力的4大特点,看完后我真的想说——网络暴力现在主要发生在网上,那实在是一大进步啊!(他这里所说的“网络暴力”应该仅指论坛言论上的攻击。)回想一下,我们花了某十年时间去进行“现实中的网络暴力”,其特点也一定程度上符合那4点,那个伤害大啊,死了多少人经济损失多少,而现在这种单纯在网上的网络暴力,幸运的话仅伤你的ID(甚至仅伤马甲),不幸的话才伤到你个人,但至少不会搞出人命。
为什么论坛能够在这种事情上那么热?因为它提供了一个“无代价战场”。有那么多自称“爱好和平”的人士,正如我所说,他们需要释放他们对暴力的欲望,这个特定场所最好就是一个无代价战场。其实无代价战场有很多选择,不过Battle Field对于很多人来说都太具有战略性,GTA的英文剧情又不那么容易懂,这时候论坛的技术零成本就绝对会让它中标了,就那么简单。
keso的东拉西扯:为什么BBS在中国那么火,我觉得既然要提早期Internet,就必须把概念分开来——BBS和Forum是两样不同的东西。虽然他们的很多特性看起来都一样,甚至使用起来也完全一样,但基于历史的原因我还是习惯把它们分开。BBS是Internet早期的产物,那时候还没有Web,消息都来自BBS,在当时的情况来说,BBS就是Internet的绝大部分,而后来的Forum则仅仅是Web的一种使用方式。BBS有一种特性,就是不可替代性,在那个时候你要获取信息就要通过BBS,你不能选择不用BBS或者用其他形式。从名字来看,BBS是电子公告版,而不是论坛,这就让BBS看起来有点官方色彩("官方"在这里指理想中的人民自愿放弃其部分政治权利而授权形成的政府)。至于为什么BBS后来在中国习惯被翻译为论坛,这可能就是问题的根源了,也就是论坛特性加上民族特性所造成的结果。
麦田在社区(1):聊天室启示录中,提到了论坛的技术零成本和言论零成本特性。技术零成本这没什么好说的,任何一个易用的产品都必须具有这样的特性。言论零成本,才是真正和民族特性结合的地方。因为中国人习惯了隐藏自己的欲望,特别是那些显得有点“坏”的欲望,然后又不同于菩提树下的顿悟——“我选择没有欲望,因此没有欲望满足不了的痛苦”,中国人会选择在适当的场合释放这些欲望,而且有可能是加倍的或者以变态的形式释放这些欲望。于是论坛就成了这样一个场所,和五谷轮回之所一样。“你有压力,我有压力”,“未解决”?那就去释放一下咯,只不过前者用于释放言论的压力,后者用于释放膀胱的压力。
在中文BBS:红旗下的蛋中,麦田还说到了“网络暴力”的问题,他总结网络暴力的4大特点,看完后我真的想说——网络暴力现在主要发生在网上,那实在是一大进步啊!(他这里所说的“网络暴力”应该仅指论坛言论上的攻击。)回想一下,我们花了某十年时间去进行“现实中的网络暴力”,其特点也一定程度上符合那4点,那个伤害大啊,死了多少人经济损失多少,而现在这种单纯在网上的网络暴力,幸运的话仅伤你的ID(甚至仅伤马甲),不幸的话才伤到你个人,但至少不会搞出人命。
为什么论坛能够在这种事情上那么热?因为它提供了一个“无代价战场”。有那么多自称“爱好和平”的人士,正如我所说,他们需要释放他们对暴力的欲望,这个特定场所最好就是一个无代价战场。其实无代价战场有很多选择,不过Battle Field对于很多人来说都太具有战略性,GTA的英文剧情又不那么容易懂,这时候论坛的技术零成本就绝对会让它中标了,就那么简单。
2006年5月22日星期一
还是喜欢工作两个月玩两个月的生活
以前习惯了,连续两个月很有热情的投入一个项目,改设计写代码,两个月之后就累了就不想做了,就算没做完也没激情了。然后开始打机,很专心的玩一个游戏,大概玩两个月又发现这个游戏不过如此,就回去做项目了。(Coo好像说过他也是这样。)
从寒假开始,接外面的东西做,其实平均下来一个月就¥400,也就是¥400分量的工作(就是隔几天做一点也行的那种),但是连续做几个月就很累,想放松,完全不想写代码。当然这也和最近写代码的方式有点相关。
以前用ASP写代码,虽然不算得上面向存储过程(因为Access根本没有Stored Procedure这个概念),但也差不多吧,反正就是我需要什么样的SQL语句,就写怎么样的,用得多的SQL语句就封装起来成为一个VBScript Function。现在用ASP.NET 2.0,尝试按照TimeTracker Starter Kit那种简单的3层架构写,但就发现写类代码就真的很累,或者应该叫做“累代码”。首先,看起来把逻辑都封装成累,对于UI代码来说是很爽的,因为UI代码再也不用管持久层需要怎样格式的数据,反正就是组织对象,用对象方法来存取数据,那就行了。但是写类代码时就会比较头痛,因为重复写那些Property并不简单,而调用方法基本上就是直接调用当前DataProvider的同名方法然后把数据传递过去。到了持久层,也就是DataProvider,需要进行类与平板的关系型数据进行交换,也是很头痛的,其实UI表现本来也是平板的嘛,对一层立体转换并不好过。还有就是,直接用存储过程可以使用一些复杂的,例如SELECT ... IN (SELECT ...)这样的嵌套语句,但是如果要先获取内SELECT的对象集合,然后for/foreach循环来获取外SELECT结果,那就惨咯。
据说有一些中型项目的项目经理还是偏好于面向存储过程设计,而不是面向对象,不知道大家看法如何。
从寒假开始,接外面的东西做,其实平均下来一个月就¥400,也就是¥400分量的工作(就是隔几天做一点也行的那种),但是连续做几个月就很累,想放松,完全不想写代码。当然这也和最近写代码的方式有点相关。
以前用ASP写代码,虽然不算得上面向存储过程(因为Access根本没有Stored Procedure这个概念),但也差不多吧,反正就是我需要什么样的SQL语句,就写怎么样的,用得多的SQL语句就封装起来成为一个VBScript Function。现在用ASP.NET 2.0,尝试按照TimeTracker Starter Kit那种简单的3层架构写,但就发现写类代码就真的很累,或者应该叫做“累代码”。首先,看起来把逻辑都封装成累,对于UI代码来说是很爽的,因为UI代码再也不用管持久层需要怎样格式的数据,反正就是组织对象,用对象方法来存取数据,那就行了。但是写类代码时就会比较头痛,因为重复写那些Property并不简单,而调用方法基本上就是直接调用当前DataProvider的同名方法然后把数据传递过去。到了持久层,也就是DataProvider,需要进行类与平板的关系型数据进行交换,也是很头痛的,其实UI表现本来也是平板的嘛,对一层立体转换并不好过。还有就是,直接用存储过程可以使用一些复杂的,例如SELECT ... IN (SELECT ...)这样的嵌套语句,但是如果要先获取内SELECT的对象集合,然后for/foreach循环来获取外SELECT结果,那就惨咯。
据说有一些中型项目的项目经理还是偏好于面向存储过程设计,而不是面向对象,不知道大家看法如何。
2006年5月15日星期一
VeryCD做开放式中文IMDB以及吃素网站
http://base.verycd.com,和IMDB非常类似的功能,主要按照作品、人来收录,然后链接作品和人的关系(例如导演、演员、配音)。不过IMDB的资料(特别是照片)都来自官方,或者专业记者,能够拿着Invitation甚至VIP证出入各种尊贵场合的那些,而VeryCD的照片则靠网友上传。
不过VeryCD的资料应该是自动从IMDB同步获取的,所以看上去更加像一个“IMDB中文外挂”。也就是说,VeryCD从IMDB的网站获取所有IMDB的文字资料,然后同步到自己的数据库,同时在自己的数据库增加IMDB没有的字段(主要是电影/演员中文名和中文简介之类的字段),然后评论是VeryCD的而不是IMDB的。还有就是,用户能够好像修改wiki那样修改VeryCD上的资料,但如果用户修改了资料然后IMDB也更新了资料,那么是不是又用IMDB的覆盖,那就不知道了(反正就是wiki类的东西,覆盖来覆盖去都没所谓)。
正所谓弱肉强食,那些靠养着一群编辑的网站已经变成绿色植物成为了食物链的源头,过去dot-com的投资已经足够多也就造成了绿色植物过多,现在是时候为了平衡一下生态环境削弱一下它们的优势了。所谓的食肉动物,就是那些打着share或者aggregate旗号直接利用别人站点内容的站点,把植物拿过来消化一下,然后变成自己的组织和能量,真是个不错的主意。既然现在绿色植物过剩,那么在接下来一段时间做做吃素网站也不错,起码不用养着一身的叶绿素,吃就行了。至于再以后,就想想办法如何做肉食动物吧,那是以后的事了……其实我是很赞成研究企业生态学的,如果一个企业能够维持好和其他有利害关系的企业的关系,不把自己的食物吃紧,同时又保证有天敌存在帮忙吃掉一些自己的同行竞争者确保这一样不过过度繁殖,那就够了。
不过VeryCD的资料应该是自动从IMDB同步获取的,所以看上去更加像一个“IMDB中文外挂”。也就是说,VeryCD从IMDB的网站获取所有IMDB的文字资料,然后同步到自己的数据库,同时在自己的数据库增加IMDB没有的字段(主要是电影/演员中文名和中文简介之类的字段),然后评论是VeryCD的而不是IMDB的。还有就是,用户能够好像修改wiki那样修改VeryCD上的资料,但如果用户修改了资料然后IMDB也更新了资料,那么是不是又用IMDB的覆盖,那就不知道了(反正就是wiki类的东西,覆盖来覆盖去都没所谓)。
正所谓弱肉强食,那些靠养着一群编辑的网站已经变成绿色植物成为了食物链的源头,过去dot-com的投资已经足够多也就造成了绿色植物过多,现在是时候为了平衡一下生态环境削弱一下它们的优势了。所谓的食肉动物,就是那些打着share或者aggregate旗号直接利用别人站点内容的站点,把植物拿过来消化一下,然后变成自己的组织和能量,真是个不错的主意。既然现在绿色植物过剩,那么在接下来一段时间做做吃素网站也不错,起码不用养着一身的叶绿素,吃就行了。至于再以后,就想想办法如何做肉食动物吧,那是以后的事了……其实我是很赞成研究企业生态学的,如果一个企业能够维持好和其他有利害关系的企业的关系,不把自己的食物吃紧,同时又保证有天敌存在帮忙吃掉一些自己的同行竞争者确保这一样不过过度繁殖,那就够了。
2006年5月13日星期六
ClaimID.com - 声明哪个是你
很有趣的服务,我是听Inside the Net的podcast听到的,可以来看看Inside the Net主持人Leo Laporte的页面:
http://claimid.com/leo
又或者ClaimID负责人之一的Fred Stutzman的:
http://claimid.com/fred
因为在线有很多同名称或者同ID的人,甚至有些是恶意冒充你的名字和ID的情况,于是有了claimID这样的服务——注册一个页面,声明哪些页面是我的或者提到的是我(例如我发布在各处的blog、我参加的项目、别人对我的评论),当然还可以声明哪些不是我。甚至,你把你的公钥(例如PGP Key)发上去也可以,这样别人就可以随时到该页面下载你的公钥,用于解密你发给他的邮件,或者其他加密信息。反正只要是URL,你就可以发上去,类似del.icio.us,不过你不是声明该URL的内容性质如何,而是该URL与你的真实身份有什么关联。
顺便说一句,我的claimID页面在这里:
http://claimid.com/cat
http://claimid.com/leo
又或者ClaimID负责人之一的Fred Stutzman的:
http://claimid.com/fred
因为在线有很多同名称或者同ID的人,甚至有些是恶意冒充你的名字和ID的情况,于是有了claimID这样的服务——注册一个页面,声明哪些页面是我的或者提到的是我(例如我发布在各处的blog、我参加的项目、别人对我的评论),当然还可以声明哪些不是我。甚至,你把你的公钥(例如PGP Key)发上去也可以,这样别人就可以随时到该页面下载你的公钥,用于解密你发给他的邮件,或者其他加密信息。反正只要是URL,你就可以发上去,类似del.icio.us,不过你不是声明该URL的内容性质如何,而是该URL与你的真实身份有什么关联。
顺便说一句,我的claimID页面在这里:
http://claimid.com/cat
2006年5月6日星期六
英语国家开发人员总是不注意Encoding的问题
英语国家开发人员通常都不考虑需要Encoding的情况,因为他们开发的软件不需要Encoding,也不一定有机会测试Encoding,这真是麻烦。
任何一段开源代码到手后你首先要考虑它是否涉及文件读写或者HTTP收发等操作,如果涉及,就必须考虑Encoding,特别是对于有多种Encoding方式的语言(例如中文有)。如果很不幸那段开源代码对Encoding没有支持,那么你就必须自己动手去增加Encoding的支持。
如果Encoding涉及的地方只有几处,那改就是了。如果Encoding遍布整个代码,那就几乎不是改的问题了,而是重写,哎……做开发不能不懂Encoding,但Encoding实际上又真的是一个大问题。有很多与计算机架构有关的问题仅限于某些计算机,因为只有那类计算机使用该架构而需要考虑该架构的问题,这是很灵活的。但是字符是任何计算平台都要交换的,不可能强制要求所有平台都转用Unicode或者一种指定的Encoding,于是设备与设备之间沟通就必须支持Encoding之间的转换。优秀的设备(例如PC)必须支持多种Encoding共存,所以运行在这些设备的软件业需要有处理多种Encoding的能力,这看起来成了一个永远无法解决的问题……
任何一段开源代码到手后你首先要考虑它是否涉及文件读写或者HTTP收发等操作,如果涉及,就必须考虑Encoding,特别是对于有多种Encoding方式的语言(例如中文有)。如果很不幸那段开源代码对Encoding没有支持,那么你就必须自己动手去增加Encoding的支持。
如果Encoding涉及的地方只有几处,那改就是了。如果Encoding遍布整个代码,那就几乎不是改的问题了,而是重写,哎……做开发不能不懂Encoding,但Encoding实际上又真的是一个大问题。有很多与计算机架构有关的问题仅限于某些计算机,因为只有那类计算机使用该架构而需要考虑该架构的问题,这是很灵活的。但是字符是任何计算平台都要交换的,不可能强制要求所有平台都转用Unicode或者一种指定的Encoding,于是设备与设备之间沟通就必须支持Encoding之间的转换。优秀的设备(例如PC)必须支持多种Encoding共存,所以运行在这些设备的软件业需要有处理多种Encoding的能力,这看起来成了一个永远无法解决的问题……
2006年4月30日星期日
设计一个面向Photoshop用户的CSS设计器,应该很畅销!
很多美工,还是习惯用Photoshop设计页面然后Slice,这样的设计方式对他们来说比较直观,不需要在脑袋里想着效果然后写HTML和CSS然后再看实现得如何。能够脑袋里想着结果然后闭上眼睛写代码的,估计主要还是程序员,美工需要能够看得到摸得着一步一步去fit的东西。虽然现在也有些美工人员在尝试摆脱HTML table排版改用CSS,但是用Photoshop输出的table改div和CSS既不方便结果也不一定是最优的(相对能够闭上眼睛写HTML+CSS的人来说)。于是,我就想如果能够开发一个有点类似Photoshop那样的绘图见面,但又能够直接用于设计CSS的设计器,应该会很多人喜欢。
其实Frontpage和Dreamweaver已经很“所见即所得”,专门的CSS编辑器如TopStyle也是能够编辑CSS的同时显示效果,为什么还要像Photoshop那样?因为我觉得美工是靠feel工作的,例如导航栏的高度和文字定位,你要求美工修改一下height然后又调整一下padding和margin,尝试一下px做单位,又换用一下%做单位,这应该是比较痛苦的事情。人家在Photoshop里面,导航栏高度喜欢拉多高就拉多高,文字定文也是用鼠标放,这完全是靠看着整个页面的构图感觉来做的,对着一堆CSS改一下代码再看一下结果就很不爽。所以,这个类似Photoshop的CSS设计器一定要完全实现在可视化界面上实现所有操作,也就是说用户也是直接通过拖动就能够实现字体定位的,至于margin和padding就让设计器自己去管理,没必要让用户操心,用户最多选一下按px计算还是按%计算然后看看不同情况下变化的效果。
要说明的是,因为美工面对的往往不是一个fixed的页面,而是随浏览器可视区域变化而变化的页面,所以设计器本身应该支持不同可视区域大小的preview。例如我当前在1024*768可视区域的模式下设计,但是旁边有缩略图显示这个CSS在800*600中的显示效果,通过鼠标放置字体位置后,改变按px计算和按%计算不会影响1024*768模式下的效果(因为这是当前设计模式),但是800*600模式下的preview就会跟随改变,因为当前的设计效果按照不同的单位来定位的话在其它模式下的显示结果是不同的。
这个设计器的重点,应该就是div和ul的放置了,因为这两个东西现在用得最多。div的放置应该支持dock(停靠)的效果,或者是现在WinForm所支持的anchor效果(比dock更好)。在说这个之前,大家可以来看看一个在线的CSS Creator的效果:
http://www.csscreator.com/version2/pagelayout.php
这个东西允许你设置常见的排版模式,例如有没有Header和Footer,它们的高度多少,按px还是按%算,body部分分为几个column(最多3个),宽度又如何算,页面的最大最小高度如何。我所谓的div支持dock,就是类似现在主流所做的那样啦,3栏结构的话,左栏dock左边、右栏dock右边、中栏占满剩余空间(这不废话),如果超过3栏也能dock/anchor的话就更好了。anchor是一个比dock更好用的东西,dock只能依赖于4页面边缘,anchor则是任何一个控件的4边缘都可以依赖于仁和另外4个附近控件的边缘,如果CSS能够实现anchor效果就好了。另外就是要有一个preview显示无CSS时的效果,能够看到完全无CSS时出来的样子如何。
其实Frontpage和Dreamweaver已经很“所见即所得”,专门的CSS编辑器如TopStyle也是能够编辑CSS的同时显示效果,为什么还要像Photoshop那样?因为我觉得美工是靠feel工作的,例如导航栏的高度和文字定位,你要求美工修改一下height然后又调整一下padding和margin,尝试一下px做单位,又换用一下%做单位,这应该是比较痛苦的事情。人家在Photoshop里面,导航栏高度喜欢拉多高就拉多高,文字定文也是用鼠标放,这完全是靠看着整个页面的构图感觉来做的,对着一堆CSS改一下代码再看一下结果就很不爽。所以,这个类似Photoshop的CSS设计器一定要完全实现在可视化界面上实现所有操作,也就是说用户也是直接通过拖动就能够实现字体定位的,至于margin和padding就让设计器自己去管理,没必要让用户操心,用户最多选一下按px计算还是按%计算然后看看不同情况下变化的效果。
要说明的是,因为美工面对的往往不是一个fixed的页面,而是随浏览器可视区域变化而变化的页面,所以设计器本身应该支持不同可视区域大小的preview。例如我当前在1024*768可视区域的模式下设计,但是旁边有缩略图显示这个CSS在800*600中的显示效果,通过鼠标放置字体位置后,改变按px计算和按%计算不会影响1024*768模式下的效果(因为这是当前设计模式),但是800*600模式下的preview就会跟随改变,因为当前的设计效果按照不同的单位来定位的话在其它模式下的显示结果是不同的。
这个设计器的重点,应该就是div和ul的放置了,因为这两个东西现在用得最多。div的放置应该支持dock(停靠)的效果,或者是现在WinForm所支持的anchor效果(比dock更好)。在说这个之前,大家可以来看看一个在线的CSS Creator的效果:
http://www.csscreator.com/version2/pagelayout.php
这个东西允许你设置常见的排版模式,例如有没有Header和Footer,它们的高度多少,按px还是按%算,body部分分为几个column(最多3个),宽度又如何算,页面的最大最小高度如何。我所谓的div支持dock,就是类似现在主流所做的那样啦,3栏结构的话,左栏dock左边、右栏dock右边、中栏占满剩余空间(这不废话),如果超过3栏也能dock/anchor的话就更好了。anchor是一个比dock更好用的东西,dock只能依赖于4页面边缘,anchor则是任何一个控件的4边缘都可以依赖于仁和另外4个附近控件的边缘,如果CSS能够实现anchor效果就好了。另外就是要有一个preview显示无CSS时的效果,能够看到完全无CSS时出来的样子如何。
2006年4月21日星期五
ASP.NET 2.0 的编译模型并非完全像 MS 说的那样
上次说到了ASP.NET 2.0解决了Code-Behind需要同步声明控件的问题,说MS的图例解释2.0中aspx和cs的内容不再是继承的关系,而是partial的关系,是合并编译。然后我说了,如果是partial关系,那么处理过程就很复杂。因为partial不能增量编译,也就不能跨语言编译,必须把aspx部分内容完全翻译为cs或者其他对应Code-Behind语言,然后才能够多partial一起编译。但实际上当然不是这样做,现实中aspx既是partial,又像1.x那样要做为继承类再编译一次。
发现这个问题,是由于我在cs声明了一个private的函数,想在aspx中的数据绑定语句中使用,结果调用时竟然告诉我找不到该函数,然后我把private改成protected就可以了。如果aspx和cs真的完全是partial关系,那么private函数是能够找到的,private找不到说明aspx中数据绑定语句和cs肯定不是在同一个类中,也就是不是partial关系,而是好像1.x那样的继承关系。实际上,我说的aspx完全翻译为Code-Behind所用语言应该是做不到的,最多把aspx中的所有服务器控件声明翻译。因为都是声明,可能如果一个partial里面有的只是声明,那编译器内部就支持增量编译,因为声明在编译结果里就是一个比较独立的部分。然后aspx中的其他逻辑都是好像1.x那样,再继承编译好的Code-Behind代码,把UI代码加上去。
发现这个问题,是由于我在cs声明了一个private的函数,想在aspx中的数据绑定语句中使用,结果调用时竟然告诉我找不到该函数,然后我把private改成protected就可以了。如果aspx和cs真的完全是partial关系,那么private函数是能够找到的,private找不到说明aspx中数据绑定语句和cs肯定不是在同一个类中,也就是不是partial关系,而是好像1.x那样的继承关系。实际上,我说的aspx完全翻译为Code-Behind所用语言应该是做不到的,最多把aspx中的所有服务器控件声明翻译。因为都是声明,可能如果一个partial里面有的只是声明,那编译器内部就支持增量编译,因为声明在编译结果里就是一个比较独立的部分。然后aspx中的其他逻辑都是好像1.x那样,再继承编译好的Code-Behind代码,把UI代码加上去。
2006年4月5日星期三
监视你的用户
正如之前的StealthMeter或者GamePlaysMan一样,我时不时都利用流行的技术和设计想出一些坏东西来,这次轮到Keep Your Visitors Under Surveillance了,咔咔……为什么那么多个词不用,偏要用Surveillance,因为我就想做到好像闭路电视监视网一样的效果,能够在中央控制室同时监视多个用户,而且要是实时的。
Analytics和MeasureMap出来了都还没有机会用上,就又听说有一个新的更强的站点分析平台能够分析用户鼠标在页面不同部位停留的时间长度,然后用不同的颜色把鼠标停留时间的梯度显示出来,虽然实际效果如何还不知道(该平台还在Alpha阶段),不过通过用户鼠标停留时间应该能够看出用户的阅读方式(还是有不少人习惯用鼠标跟随目光的),以及用户对页面的哪些链接感兴趣(即使没有点击)。另外最近也看了Sliver(1993),觉得在监控室中对着满墙的监视屏幕看着不同房间的人在做不同的事会挺有趣,于是就产生了监视Web用户这个想法。
首先,要实现一种ClientViewState,也就是客户端的ViewState。和ASP.NET的服务器端ViewState用于保持页面生命周期间View的状态不同,ClientViewState就是显示反映客户端浏览器当前的状态,例如页面滚动到何处,用户鼠标停留在哪里。这个ClientViewState要是实时更新的,也就是客户端的所有操作相应都实时更新ClientViewState,对于好像鼠标移动这样的事情ClientViewState会产生Frame,而对于点击这样的更新产生KeyFrame,Frame和KeyFrame会使用Ajax模式直接传输到服务器以更新服务器端存储的ClientViewState,再传输时KeyFrame拥有更高的优先级,一旦产生KeyFrame则KeyFrame之前产生但未传输完的Frame就丢弃,仅当没有连续KeyFrame时才传送Frame。
然后,监视页面从服务器下载ClientViewState下来(使用Comet模式),把每一个ClientViewState释放到一个对应的IFrame,在该IFrame还原对应的被监视用户的当前ClientViewState,通过IE的DOM的按百分比缩小显示功能把IFrame内的内容缩小,这时候监视者就可以在监视页面看到用户实际操作的情况了。
实现ClientViewState不是难事,现在的DOM足以应付,然后把ClientViewState串行话传导服务器端然后再传到另一个客户端并还原也不难,关键就是传输途中用的Comet模式是否有效率。所谓Comet模式就是现在Talk in GMail和Meebo等WebIM采用的Ajax模式,和普通Ajax唯一不同的就是Comet使用keep-alive的HTTP连接从而实现服务器端主动Push数据到客户端,但到底这样的长连接能够应付多大的负载我实在还不知道,不过应该应付串行话的ClientViewState传输还是绝对没问题的。
至于实时监视有没有什么意义,这个我暂时不知道,对于成熟的站点来说可能没什么意义,但是对于Beta阶段需要知道用户使用方式的站点来说或许有用,正如软件易用性测试可以用走廊测试一样(在走廊上随机拦几个人来试用你的软件然后看他们的操作方式),你可以通过监视用户如何使用你的站点从而知道易用性方面是否有问题,同时也无需让用户填一堆关于使用方式的调查问卷。
Analytics和MeasureMap出来了都还没有机会用上,就又听说有一个新的更强的站点分析平台能够分析用户鼠标在页面不同部位停留的时间长度,然后用不同的颜色把鼠标停留时间的梯度显示出来,虽然实际效果如何还不知道(该平台还在Alpha阶段),不过通过用户鼠标停留时间应该能够看出用户的阅读方式(还是有不少人习惯用鼠标跟随目光的),以及用户对页面的哪些链接感兴趣(即使没有点击)。另外最近也看了Sliver(1993),觉得在监控室中对着满墙的监视屏幕看着不同房间的人在做不同的事会挺有趣,于是就产生了监视Web用户这个想法。
首先,要实现一种ClientViewState,也就是客户端的ViewState。和ASP.NET的服务器端ViewState用于保持页面生命周期间View的状态不同,ClientViewState就是显示反映客户端浏览器当前的状态,例如页面滚动到何处,用户鼠标停留在哪里。这个ClientViewState要是实时更新的,也就是客户端的所有操作相应都实时更新ClientViewState,对于好像鼠标移动这样的事情ClientViewState会产生Frame,而对于点击这样的更新产生KeyFrame,Frame和KeyFrame会使用Ajax模式直接传输到服务器以更新服务器端存储的ClientViewState,再传输时KeyFrame拥有更高的优先级,一旦产生KeyFrame则KeyFrame之前产生但未传输完的Frame就丢弃,仅当没有连续KeyFrame时才传送Frame。
然后,监视页面从服务器下载ClientViewState下来(使用Comet模式),把每一个ClientViewState释放到一个对应的IFrame,在该IFrame还原对应的被监视用户的当前ClientViewState,通过IE的DOM的按百分比缩小显示功能把IFrame内的内容缩小,这时候监视者就可以在监视页面看到用户实际操作的情况了。
实现ClientViewState不是难事,现在的DOM足以应付,然后把ClientViewState串行话传导服务器端然后再传到另一个客户端并还原也不难,关键就是传输途中用的Comet模式是否有效率。所谓Comet模式就是现在Talk in GMail和Meebo等WebIM采用的Ajax模式,和普通Ajax唯一不同的就是Comet使用keep-alive的HTTP连接从而实现服务器端主动Push数据到客户端,但到底这样的长连接能够应付多大的负载我实在还不知道,不过应该应付串行话的ClientViewState传输还是绝对没问题的。
至于实时监视有没有什么意义,这个我暂时不知道,对于成熟的站点来说可能没什么意义,但是对于Beta阶段需要知道用户使用方式的站点来说或许有用,正如软件易用性测试可以用走廊测试一样(在走廊上随机拦几个人来试用你的软件然后看他们的操作方式),你可以通过监视用户如何使用你的站点从而知道易用性方面是否有问题,同时也无需让用户填一堆关于使用方式的调查问卷。
2006年4月3日星期一
LoadPostData 最多只有两次
在很多MSDN的一般文档中,都回避了LoadPostData这个问题,直接就说Page由Init到Load然后就是控件事件了。而根据我的实验,在Load的前后会出现两次的LoadPostData,而最后在MSDN也找到了独立的文章证实了这个流程。
一般的HTML中静态声明的控件初始化应该发生在Init,Init之后这些控件也就初始化完成。然后此时会有一次LoadPostData,这是面向仅使用HTML静态声明控件的情况,这样在Load的时候这些静态声明的控件就已经成功加载了数据,可以直接访问这些数据做一些事情了(例如根据这些数据动态创建控件)。在Load之后,如果确实有动态创建了控件,那么第二次LoadPostData就会被调用,用于为这些动态创建的控件加载数据,如果在上一次页面生命周期这些控件也有被动态创建,那么此时它们的数据就得以加载到本次页面生命周期的对应控件实例。再之后就是控件事件阶段了,所有控件都根据自己加载回来的数据判断是否要引发什么事件。
动态创建控件的最迟不能晚于第二次LoadPostData,否则创建的控件在下一个生命周期就算再次创建同样的控件也不能成功加载数据,这就确定了动态创建控件不能放到控件事件中进行,因为控件事件发生在LoadPostData后。(理论上页面生命周期可以设计为递归型的,也就是每次引发控件事件后再检查是否有添加新控件,有的话再LoadPostData,LoadPostData后再检查这些控件是否需要引发事件,不过实际上页面生命周期没有这样设计。)基本上动态创建控件必须在Load前后完成,例如开发复合控件或者模板控件时常在CreateChildControls和DataBinding中做此项工作。
如果涉及复杂的控件创建,这些控件创建必须依赖于事件改怎么办?这就只有一个办法,把动态创建变成创建所有并动态隐藏。例如制作一个有很复杂规则的调查问卷,根据答题者不同的选择转跳来转跳去(这时候你可能首先会想到ASP.NET 2.0的Wizard控件或者CrossPagePostBack,但这样每次仅显示当前的一个问题),先在页面上创建所有题目并且在Load时根据当前答题情况隐藏不应该出现的问题就是一个解决方案。这里隐藏的关键,就在于隐藏的控件不呈现到HTML上(或呈现到HTML但隐藏),但它们的ViewState却照样保存,这就使得所有的题目实际上都能够跨越页面生命周期而存在并且实现数据持久。
不过Page级别的隐藏方法好像不那么美观,所以我们应该这些逻辑封装到一个WebControl里。既然封装到WebControl里,也就没必要让各个隐藏状态的控件自行保存ViewState,直接把它们的属性统一集中到父控件并保存到其ViewState就行了。此时所有子控件的数据加载变成了由父控件统一控制,当一个子控件不需要出现时就不再需要先创建再隐藏而根本就不用创建,因为父WebControl会继续负责此子控件数据的持久。
其实ASP.NET 2.0的Wizard可能也是用类似的方式实现。在ASP.NET 1.1时我就提出过Wizard,觉得这是一个非常容易实现的控件(就按照上面的父控件统一实现子控件数据持久的方法),而且也很有价值,就是奇怪为什么各大WebControl开发商不做一个出来,结果到ASP.NET 2.0官方就做了一个出来了。至于CrossPagePostBack,用的也就是没有控件但ViewState保留的做法,每次CrossPagePostBack都把PreviousPage的整个ViewState保留下来,不过唯一问题就是如果连续用很多个CrossPagePostBack实现一个Wizard那么多个ViewState堆叠下来占用的传输带宽也一定不少。
所以说,在ASP.NET里面用好ViewState很关键(这个下次详细说),如果还是没ViewState的思想,还是按照其它动态页面语言的思路去做,那是用不好ASP.NET的控件模型的。
一般的HTML中静态声明的控件初始化应该发生在Init,Init之后这些控件也就初始化完成。然后此时会有一次LoadPostData,这是面向仅使用HTML静态声明控件的情况,这样在Load的时候这些静态声明的控件就已经成功加载了数据,可以直接访问这些数据做一些事情了(例如根据这些数据动态创建控件)。在Load之后,如果确实有动态创建了控件,那么第二次LoadPostData就会被调用,用于为这些动态创建的控件加载数据,如果在上一次页面生命周期这些控件也有被动态创建,那么此时它们的数据就得以加载到本次页面生命周期的对应控件实例。再之后就是控件事件阶段了,所有控件都根据自己加载回来的数据判断是否要引发什么事件。
动态创建控件的最迟不能晚于第二次LoadPostData,否则创建的控件在下一个生命周期就算再次创建同样的控件也不能成功加载数据,这就确定了动态创建控件不能放到控件事件中进行,因为控件事件发生在LoadPostData后。(理论上页面生命周期可以设计为递归型的,也就是每次引发控件事件后再检查是否有添加新控件,有的话再LoadPostData,LoadPostData后再检查这些控件是否需要引发事件,不过实际上页面生命周期没有这样设计。)基本上动态创建控件必须在Load前后完成,例如开发复合控件或者模板控件时常在CreateChildControls和DataBinding中做此项工作。
如果涉及复杂的控件创建,这些控件创建必须依赖于事件改怎么办?这就只有一个办法,把动态创建变成创建所有并动态隐藏。例如制作一个有很复杂规则的调查问卷,根据答题者不同的选择转跳来转跳去(这时候你可能首先会想到ASP.NET 2.0的Wizard控件或者CrossPagePostBack,但这样每次仅显示当前的一个问题),先在页面上创建所有题目并且在Load时根据当前答题情况隐藏不应该出现的问题就是一个解决方案。这里隐藏的关键,就在于隐藏的控件不呈现到HTML上(或呈现到HTML但隐藏),但它们的ViewState却照样保存,这就使得所有的题目实际上都能够跨越页面生命周期而存在并且实现数据持久。
不过Page级别的隐藏方法好像不那么美观,所以我们应该这些逻辑封装到一个WebControl里。既然封装到WebControl里,也就没必要让各个隐藏状态的控件自行保存ViewState,直接把它们的属性统一集中到父控件并保存到其ViewState就行了。此时所有子控件的数据加载变成了由父控件统一控制,当一个子控件不需要出现时就不再需要先创建再隐藏而根本就不用创建,因为父WebControl会继续负责此子控件数据的持久。
其实ASP.NET 2.0的Wizard可能也是用类似的方式实现。在ASP.NET 1.1时我就提出过Wizard,觉得这是一个非常容易实现的控件(就按照上面的父控件统一实现子控件数据持久的方法),而且也很有价值,就是奇怪为什么各大WebControl开发商不做一个出来,结果到ASP.NET 2.0官方就做了一个出来了。至于CrossPagePostBack,用的也就是没有控件但ViewState保留的做法,每次CrossPagePostBack都把PreviousPage的整个ViewState保留下来,不过唯一问题就是如果连续用很多个CrossPagePostBack实现一个Wizard那么多个ViewState堆叠下来占用的传输带宽也一定不少。
所以说,在ASP.NET里面用好ViewState很关键(这个下次详细说),如果还是没ViewState的思想,还是按照其它动态页面语言的思路去做,那是用不好ASP.NET的控件模型的。
2006年3月31日星期五
Cassini其实不是新东西,早在2000年就有离线ASP宿主
详细可以参考这两篇文章:
http://msdn.microsoft.com/msdnmag/issues/0900/cutting/default.aspx
http://msdn.microsoft.com/msdnmag/issues/1000/cutting/default.aspx
那时候,设计的目标是为了做Demo CD,而作为Demo CD不能够要求客户机上必须装有IIS,所以就需要设计一个轻量级的离线的ASP Host。在2000年,这两篇文章归到Cutting Edge去了,不过这样的技术在当时来说确实非常Cutting Edge了,一般Demo CD最多内附一个PWS(Personal Web Server)让Win9x也能装上看看ASP的效果,基本上没有人会想到要实现自己的ASP Host。这不仅仅是技术问题,而且还是思想问题,RIA还没有提出,用HTML或者Flash做Windows应用还是非常罕见,没有人有理由要实现一个随处可运行的展示给localhost看的Web Host。
现在Cassini来了,靠的主要是System.Web.Hosting这个namespace下面的东西。其实要实现ASP.NET Host关键就是要把Request流的内容倒入HttpWorkerRequest,之后HttpWorkerRequest配合HttpRuntime就是你所要的一切输入了,而且是完整封装好和普通ASP.NET无异的了。至于输出,Cassini同样是需要实现原本HttpResponse直接让IIS实现的功能,自行把Response流的各种Header写入,然后写Response流的主体。
System.Web.Hosting其实是ASP.NET 1.x就有的东西,看来MS一早就准备了要让ASP.NET可以脱离IIS运行。如果非Windows平台上的.NET Framework能够弄出来,就能够轻易地把ASP.NET迁移过去。现在也有人在搞.NET Compact Framework上的ASP.NET Hosting(因为.NET CF没有System.Web.Hosting),那么ASP.NET就能够成为全平台的Web应用了哦,既能在线也能离线。
http://msdn.microsoft.com/msdnmag/issues/0900/cutting/default.aspx
http://msdn.microsoft.com/msdnmag/issues/1000/cutting/default.aspx
那时候,设计的目标是为了做Demo CD,而作为Demo CD不能够要求客户机上必须装有IIS,所以就需要设计一个轻量级的离线的ASP Host。在2000年,这两篇文章归到Cutting Edge去了,不过这样的技术在当时来说确实非常Cutting Edge了,一般Demo CD最多内附一个PWS(Personal Web Server)让Win9x也能装上看看ASP的效果,基本上没有人会想到要实现自己的ASP Host。这不仅仅是技术问题,而且还是思想问题,RIA还没有提出,用HTML或者Flash做Windows应用还是非常罕见,没有人有理由要实现一个随处可运行的展示给localhost看的Web Host。
现在Cassini来了,靠的主要是System.Web.Hosting这个namespace下面的东西。其实要实现ASP.NET Host关键就是要把Request流的内容倒入HttpWorkerRequest,之后HttpWorkerRequest配合HttpRuntime就是你所要的一切输入了,而且是完整封装好和普通ASP.NET无异的了。至于输出,Cassini同样是需要实现原本HttpResponse直接让IIS实现的功能,自行把Response流的各种Header写入,然后写Response流的主体。
System.Web.Hosting其实是ASP.NET 1.x就有的东西,看来MS一早就准备了要让ASP.NET可以脱离IIS运行。如果非Windows平台上的.NET Framework能够弄出来,就能够轻易地把ASP.NET迁移过去。现在也有人在搞.NET Compact Framework上的ASP.NET Hosting(因为.NET CF没有System.Web.Hosting),那么ASP.NET就能够成为全平台的Web应用了哦,既能在线也能离线。
Macro CSS, CSS Script or Xml CSS?
继续上次那篇Programmable CSS,我现在有三个实现方案:
1.Macro CSS
用Macro来实现简单的CSS编程,既然CSS是纯文本输出,那么可以考虑仿照ASM/C++的Macro那样,定义一些简单的文本输出Macro,例如Coo所谓的自定义颜色常量就完全可以好像C++定义常量那样定义。页面就引用一个.mcss文件,这个后缀在服务器端关联一个HttpHandler(假设在.NET环境,其他语言环境自行变通),专门负责处理.mcss文件里面的Macro输出标准css。
这样做需要对Macro CSS用正则表达式进行分析,提取指令,还要做变量存储,我觉得这是比较麻烦的事情。例如变量存储,我上次说了要实现子元素可以设定padding为父元素padding的加上1px,这就需要允许在Macro里面定义变量,然后Macro运行时这些变量是无类型的,或者说是动态类型的,要支持各种运算,烦死,简直就是要自己写一个Macro Engine。
不写Macro Engine的方法有没有?也还是有的,就是把Macro转换为对应的服务器端脚本,例如ASP.NET,然后让服务器帮你编译执行,这样会舒服很多。这和直接写个满是<% %>的CSS有什么不同?我们可以为Macro CSS编写转换到不同服务器端语言的引擎,然后同一种Macro CSS就能够运行于任意动态服务器端了。
2.CSS Script
这个东西我不确定是否可行,其实就是完全用Script实现页面CSS的操作。
CSS Script拥有类似Atlas那样的一套完整的、兼容多种浏览器的Framework,然后能够用Javascript按照类似C#的写法或者类似XML的写法那样直接操作一些Framework中的封装好的CSS对象。简而言之,这就是一个CSS版的Atlas,Atlas用于DHTML和WebService,而CSS Script则用于CSS。或许未来的Atlas将会把CSS操作也封装进去,难说呢。没看过Atlas的一定要先去看看,否则会不明白我说什么的,因为Atlas实在是太过高度封装了。
3.Xml CSS
就是直接用XML来描述一个CSS模型,然后用XSLT来生成CSS,实现起来很简单,只不过用XML来写CSS或许会让人很不习惯,因为要写多很多代码。不过如果有IDE工具支持的话,或许大家都愿意接受。
原本的CSS也是DOM的一部分,除去HTML的DOM,CSS也可以独立成DOM。能够独立成DOM自然也就能够用XML来表示。用XML来表示,就可以向这个DOM增加新的元素,例如常量、运算、条件等,而新增的这些元素都通过XSLT解释最终生成普通的CSS就行了。
1.Macro CSS
用Macro来实现简单的CSS编程,既然CSS是纯文本输出,那么可以考虑仿照ASM/C++的Macro那样,定义一些简单的文本输出Macro,例如Coo所谓的自定义颜色常量就完全可以好像C++定义常量那样定义。页面就引用一个.mcss文件,这个后缀在服务器端关联一个HttpHandler(假设在.NET环境,其他语言环境自行变通),专门负责处理.mcss文件里面的Macro输出标准css。
这样做需要对Macro CSS用正则表达式进行分析,提取指令,还要做变量存储,我觉得这是比较麻烦的事情。例如变量存储,我上次说了要实现子元素可以设定padding为父元素padding的加上1px,这就需要允许在Macro里面定义变量,然后Macro运行时这些变量是无类型的,或者说是动态类型的,要支持各种运算,烦死,简直就是要自己写一个Macro Engine。
不写Macro Engine的方法有没有?也还是有的,就是把Macro转换为对应的服务器端脚本,例如ASP.NET,然后让服务器帮你编译执行,这样会舒服很多。这和直接写个满是<% %>的CSS有什么不同?我们可以为Macro CSS编写转换到不同服务器端语言的引擎,然后同一种Macro CSS就能够运行于任意动态服务器端了。
2.CSS Script
这个东西我不确定是否可行,其实就是完全用Script实现页面CSS的操作。
CSS Script拥有类似Atlas那样的一套完整的、兼容多种浏览器的Framework,然后能够用Javascript按照类似C#的写法或者类似XML的写法那样直接操作一些Framework中的封装好的CSS对象。简而言之,这就是一个CSS版的Atlas,Atlas用于DHTML和WebService,而CSS Script则用于CSS。或许未来的Atlas将会把CSS操作也封装进去,难说呢。没看过Atlas的一定要先去看看,否则会不明白我说什么的,因为Atlas实在是太过高度封装了。
3.Xml CSS
就是直接用XML来描述一个CSS模型,然后用XSLT来生成CSS,实现起来很简单,只不过用XML来写CSS或许会让人很不习惯,因为要写多很多代码。不过如果有IDE工具支持的话,或许大家都愿意接受。
原本的CSS也是DOM的一部分,除去HTML的DOM,CSS也可以独立成DOM。能够独立成DOM自然也就能够用XML来表示。用XML来表示,就可以向这个DOM增加新的元素,例如常量、运算、条件等,而新增的这些元素都通过XSLT解释最终生成普通的CSS就行了。
订阅:
博文 (Atom)