代码改变世界

英语阅读推荐:使用AJAX+WF+LINQ制作Google IG式首页

2007-01-16 01:28  Cat Chen  阅读(8451)  评论(25编辑  收藏  举报

如果你既想学习ASP.NET AJAX,又想学习Windows Workflow Foundation,还想学习LINQ(包括DLinq和XLinq),能够一次过满足你三个愿望的除了Kinder出奇蛋就是本期Random Clipping重点推荐的这篇文章了。

Build Google IG like Ajax Start Page in 7 days using ASP.NET Ajax and .NET 3.0

点击链接打开文章后,相信大部分读者的第一反应都是“哇!那么长,怎么可能看得完啊!”既然文章标题说是用7天时间来制作一个这样的首页,那么你也可以先用del.icio.us把文章收藏下来然后分开几天看。想学新东西当然就要花点时间的啦,当你耐心把Omar Al Zabir这7天的开发记录读完,再结合源代码深入理解一下它的设计,相信你就能对它所用的几种新技术都会有所了解,至少知道在什么开发场景下使用什么能够为你节省时间提高效率。如果你觉得本文也太长了,那么也可以考虑先收藏下来,等到有时间了再看。

简介

首先,我们来看看工作记录之前的介绍部分。第一节的Introduction说明了这个项目的实现结果是比较贴近真正的Google IG的,它允许拖放小部件(widget)、完全个性化的页面、支持多页面等。并且说明它不是一个原型项目或样例项目,而是一个真正在运作的开源的首页:http://www.dropthings.com/。你可以在日常生活中使用它,也可以使用ASP.NET为它开发更多的小部件。

如果你看到这里,还是不是很明白所谓的“首页”是什么,第二节的What is an Web 2.0 Ajax Start page给出了详细的解释。接着第三节的Features简单说明了其功能,其实这些功能就和Google IG几乎完全一样,没有添加任何新的东西。我觉得这个项目对比Google IG,真正应该强调的功能是你可以好像开发普通UserControl那样为它设计小部件(widget),而不需要懂XML和JavaScript,而这正是第四节Widgets所讲的。第五节Technologies说了此项目所用到的技术,看不到ASP.NET AJAX Future CTP字眼吧?那意味着你无需伸手去碰JavaScript了,AJAX实现所需要的最多就是编写服务器端代码调用一下UpdatePanel和Extender。

分层

接下来从分层的角度来了解一下此项目的特色层,包括Web Layer和Data Access using DLinq。在Web Layer小节,作者强调的是“整个应用也就是一个页面”,并且例如加载当前用户设置这样的常见任务是通过调用Workflow来完成的,还通过实测数据说明了Workflow自身的运行时间是很短的。

在Data Access using DLinq小节,作者强调的是使用DLing所附带的SqlMetal.exe来直接生成数据访问类,这些类包含了所有的数据访问代码以及实体(entity)类。然后作者拿DLing根第三方ORM工具比较,说明DLing特有的Projection技术能够根据你所需要的字段生成对应的属性,这个依赖于定制编译器的功能所以是第三方ORM工具暂时无法实现的,它给你带来的好处就是效率的提升,因为你实际上并没有SELECT那些你不会用到的字段。作者还说,如果你不相信DLinq的表现,或者对DLinq抱有一种不怎么好的感觉的话,可以直接用SQL Profiler看看DLinq让数据库实际执行的SQL语句到底是怎么样的。

第一天:使用UpdatePanel制作Widget容器

正所谓万事开头难,第一天要设计的Widget容器也是不容易的,别看最终生成的就是简单的几层嵌套div,要让它能展开/折叠和能被拖放,并且执行起来符合效率要求,就不那么容易。

在这一节里面,你需要重点注意到的就是Extender放置问题,这让设计如何嵌套成为了一个难题。假如Extender放置在UpdatePanel内,每次UpdatePanel更新都会导致整个Extender的客户端代码被销毁然后重新生成,也就要重新初始化一次,最终导致效率低下。因此,要尽量确保Extender都在UpdatePanel的外面,而最终的嵌套设计方案可以看作者的图示。

至于WidgetContainer,它继承自IWidgetHost,这种设计为将来的扩展预留了足够的空间。当前设计的Widget,可以依赖于WidgetContainer的一些特有的功能,也可以仅仅依赖于IWidgetHost从而将来适应其他IWidgetHost容器。

第二天:制作定制的拖放Extender和多栏放置区域

在这一节里面,作者解释了为什么原有的拖放Extender都不适用于此项目的情景,从而需要自己设计一个新的Extender。其实我也认为ASP.NET AJAX和Control Toolkit自带的各种拖放功能确实限制多多,并不能根据你的开发场景所需要灵活定制,所以使用ASP.NET AjAX时几乎可以肯定如果你需要使用拖放就要自己写。当然,这不一定需要从头写起,Future CTP内置了客户端的拖放管理器,你只需要实现两个接口就可以实现拖放。

另外大多数的Extender都是通过异步Postback时在服务器端保存状态,而这对于的首页来说是不可能的,因为用户一次拖放后很可能马上就要进行下一次的拖放,所以这个定制的拖放Extender通过调用Web Service来保存状态。

第三天:制作数据访问层和站点加载

作者在这一节里展示了此项目的数据库设计,并且说明了如何跨层地通过DLinq访问数据。跨层使用DLinq的问题就在于,如果你的实体用于各个不同的层,那么它就从加载它的DataContext分离(detach)出来了,通常业务逻辑层是没有DataContext的,所以你要将它发回到数据访问层然后利用DataContext更新,通常我们的做法是业务逻辑层修改实体然后再发回给数据访问层,而DLinq则要求先把实体附加(attach)到DataContext再作出修改然后提交更新。

作者使用了delegate来解决这个问题,调用数据访问层的更新函数时传入一个delegate,然后数据访问层把实体附加到DataContext,接着调用该delegate,最后提交更新。而该delegate指向的函数则放在业务逻辑层,这样就保持了原来的分层,并且按照DLinq所需的方式来完成更新,虽然这导致调用在两个层之间往返多了一次。另外,同样的设计方式可用于插入与删除。

第四天:使用XLinq制作Flickr照片与RSS小部件

作者想做的第一个小部件用于显示Flickr照片。首先通过XElement把RSS加载上,然后使用XLinq将XML影射为对象集合,接着还是通过XLinq迭代集合中特定范围的对象并且构建对应的控件树,就那么简单,通用的RSS小部件也用类似的方式制作。想象一下如果没有XLinq你会如何去完成这部分功能,再看看作者给出的代码,你就知道XLinq为你节省多少时间了。

值得注意的是,作者使用了HtmlGenericControl来生成链接,而不是用HtmlLink,因为HtmlLink不允许有子控件,这是它特有的一个局限,而HtmlGenericControl就能很好的解决此问题。

第五天:制作业务逻辑层中的工作流

这是问题多多的一天,把DLinq和WinFX混合在一起不是一件容易的事情,作者就需要解决以下问题:

  • 在ASP.NET中同步调用Workflow
  • Workflow运行完后将对象取出来
  • 在一个Workflow同步调用另一个Workflow

第一个问题通过对WorkflowRuntime添加两个Service解决了。第二个问题,通过创建一个叫做CallWorkflowActivity的新Activity来解决,这个Activity的实现非常复杂,有兴趣了解其细节的朋友可以看这篇文章:A workflow sample。由于Workflow本身是设计为异步执行的,然而ASP.NET的服务器端处理却要求是同步的,所以才会遇到这两个问题。第三个问题源自Workflow是设计为可休眠的,这时候就要对它的数据进行序列化然后持久,然而DLinq的实体却不是可序列化的。解决这个问题的方式是把DLinq实体放到Dictionary<string, object>里面,从而通过要求对象可序列化的检查。

另外要在同一个项目里同时加载WinFX和DLinq的编译器是需要通过修改项目文件的,作者给出了修改方法。不过修改后Workflow的声明性Rules无法正常编译,因为.rules文件无法被正常识别为嵌入资源,这看起来是一条死路。直到第二天清晨,周围一片寂静,太阳准备升起,作者听到了来自天堂的神圣启示,然后明白到如何把这个问题解决掉,想了解详情的就仔细读读原文吧。

第六天:页面切换问题

页面切换时,新页面的小部件被第一次加载,而如果它们通过IsPostBack属性判断自己是否第一次加载的话,就会得到错误的信息,然后尝试根据ViewState恢复上一次的状态也就会失败,从而无法正常初始化。解决方法自然是通过WidgetContainer主动通知Widget这是不是你的第一次加载。

在这里我要加插一些题外话,我觉得这不是一个有效解决问题的办法。如果你设计一个Widget,在上面扔一个DataSourceControl和一个DataBoundControl,就可能导致加载失败,因为这些控件本身就设计为依赖于IsPostBack属性,它们自动根据IsPostBack属性来决定是否需要数据绑定。希望ASP.NET 3.0将这个问题解决了,因为我们确实会有需要动态加载DataBoundControl的时候,它们第一次加载时需要进行数据绑定,而此时IsPostBack属性却为true。

第七天:注册

是时候准备迎来第一个注册用户了,这时候需要将他在匿名访问时使用的设置复制到注册用户中去。因为匿名用户的信息不保存在aspnet_membership,而仅保存在aspnet_user和aspnet_profile,所以不能使用Membership.GetUser获取到匿名用户的实体然后获取其设置,这时候就需要手动访问数据库完成上述复制操作了。

在使用DLinq查询ASPNETDB之前,首先你要看Omar Al Zabir的另一篇文章:Careful when querying on aspnet_users, aspnet_membership and aspnet_profile tables used by ASP.NET 2.0 Membership and Profile provider。ASPNETDB里面的索引都是使用ApplicationId和LowerUserName或LowerEmail组合的,为了确保查询时通过索引以确保效率,你也必须使用上述组合作为查询条件。

收尾

因为这个项目混合了ASP.NET AJAX、WinFX和LINQ,所以必须有一个混合型的web.config以令它们和谐共处,这正是Web.config walkthrough小节要说的事情。接着Deployment Problem和How to run the code两个小节说明了你应该如何部署这个项目,并且让它运行起来。之后Next Steps小节说明了你可以使用ASP.NET为此项目开发更多的小部件,而Conclusion小节则说明了混合这3种技术到一个项目里是多么有挑战性的事情,不过它们也大大降低了你的开发难度。

总结

中间我跳过了How slow is ASP.NET Ajax这一小节没说,在这里作者说明了当一个页面的UpdatePanel和Extender的数量增加到一定程度时,客户端的执行速度将变得非常低。后来作者根据Scott Guthrie的提醒,在web.config中设置debug="false",这将关闭客户端脚本运行时的验证功能,执行速度已经比较好,在IE7、FF和Opera9中速度有明显的提升,然而在IE6中还是比较慢。

这说明了对于只有一个页面的AJAX应用来说,使用UpdatePanel和Extender并非是一个很好的解决方案,除非你有足够的能力通过各式hacking来提升执行效率。然而我觉得,如果你有那样的能力,干脆就用Future CTP开发以客户端为中心的AJAX应用好了,可以尽量减少对服务器端控件与Extender的依赖。

最后,感谢你阅读本期Cat in dotNET(http://purl.oclc.org/NET/CatChen/dotNET)的Random Clipping。你可以通过订阅Cat in dotNET以获取更多此类文章,Feed订阅地址为:http://feeds.feedburner.com/CatChen/dotNET,Email订阅地址为:http://www.feedburner.com/fb/a/emailverifySubmit?feedId=573799