MagicAjax.NET is an open-source framework designed to make it easier and more intuitive for developers to integrate AJAX technology into their web pages, without replacing the ASP.NET controls and/or writing tons of javascript code.
From www.magicajax.net
和其他流行的ajax框架,比如ajax.net等相比,magicajax的设计思路很有特点,同样是利用ajax技术,他的方便和易用十分地引人注目。
从开发人员使用角度讲,在ajax.net框架下,我们需要注册一个公布给客户端的类,然后在客户端脚本里访问,然后再根据从服务端返回的数据自己控制客户端界面的改变,使用起来不是十分方便。而在magicajax的框架下,我们甚至不需要写一行代码就实现了无刷新的网页,在浏览器里工作就像在winform里一样,给用户以无缝体验,大大地简化了开发人员的工作。
从设计架构的角度讲,ajax.net框架参考了prototype.js这个纯脚本ajax框架,如果你注册了一个类型到ajax.net里,那么在输出到客户端的脚本里就会有ajax.net为你生成的javascript对象,开发人员就像调用服务端对象一样使用,其实背后都是用脚本模拟的,说起来倒有点像remoting的对象截获。当然,如果需要显示什么东西的话,比如填充某个下拉框就得自己动手,操作dhtml。而magicajax则是另外一种思路,我们不是要页面无刷新吗?OK,只要把你要执行服务端事件的控件放到ajapanel里,magicajax就在后台将页面要提交的数据提交回去,交给IIS,再传递给注册在配置文件中的ajaxmodule,他会负责返回被其框架解析的脚本语句,来反映出服务器操作造成的变化,客户端eval一下就可以了。而如何得到实现这种变化的脚本语句就成为代码主要完成的工作。
从创新的角度讲,通过使用储存页面的配置,magicAjax几乎改变了web的编程模型,真正意义上实现了桌面程序模型的web编程。在这后面分析源码的过程中再详细介绍。
前面的叙述只是个轮廓,现在开始一步步地解析magicajax的源码。Magicajax前不久发布了0.2.2,由于变化不是太大,我以0.2.1的源码作为分析的样本。
如果要使用magixajax,除了引用dll以外,我们还需在配置文件里加上:
/// <httpModules> /// <add name="MagicAjaxModule" type="MagicAjax.MagicAjaxModule, MagicAjax" /> /// </httpModules>
我们可以从作用的入口点:MagicAjaxModule 开始分析。
MagicAjaxModule实现了IHttpModule接口,实现IHtppModule的Init方法,绑定应用程序HttpApplication的BeginRequest, AcquireRequestState, EndRequest事件。
在BeginRequest阶段,MA储存了当前上下文的Request和Response变量,这是后面分析请求以及输出脚本所必需的对象。这阶段还对AjaxCallObject.js.aspx的请求做了响应,输出AjaxCallObject.js脚本。
最主要的处理则在AcquireRequestState阶段。考虑到页面的储存与否会导致两种截然不同的编程模型,为了描述方便,我们对两种pagestore方式分别展开讨论。 首先,对这部分代码的分析我们按<pageStore mode="NoStore">的配置进行分析。在这个部分,如果请求为get或非aspx页面则不进行处理,如果pagesstore=nostore,则让asp.net的框架根据提交的页面生成新的页面:
HttpContext.Current.Handler.ProcessRequest(HttpContext.Current); 按照正常的页面生命周期,那magicpanel输出的Html会是什么呢?上述方法完成后的代码为:
_response.Flush(); string vsValue = _filter.GetViewStateFieldValue(); if (vsValue != null && _request.Form["__VIEWSTATE"] != vsValue) { AjaxCallHelper.WriteSetFieldScript("__VIEWSTATE", vsValue); } AjaxCallHelper.End();
这里AjaxCallHelper储存了写viewstate的脚本。可以调试查看在AjaxCallHelper.End()方法完成以前,Response里向客户端输出的内容里还是整个页面的代码。在AjaxCallHelper.End()执行后,输出的代码就成了页面内容变动的那部分的javascript代码。AjaxCallHelper.End()的代码如下:
public static void End() { if (_writingLevel > 0) throw new MagicAjaxException("Script writing level should be 0 at the end of AjaxCall. IncreaseWritingLevel calls do not match DecreaseWritingLevel calls.");
WriteEndSignature();
HttpResponse hr = HttpContext.Current.Response;
hr.Clear(); MergeNextWritingLevelRecursive (0); hr.Write ((_sbWritingLevels[0] as StringBuilder).ToString());
MagicAjaxContext.Current.CompletedAjaxCall = true;
hr.Flush(); hr.End(); }
从上面我们可以看到Response在将输出到客户端时被替换掉了,呵呵,好个狸猫换太子啊!最后输出的是sbWritingLevels中的一个StringBuilder的文本。如果你在代码里有Response.Write的代码输出文本的话是看不到效果的。这里说明一下,MA维护了一个StringBuilder的集合,因为在服务端控件输出脚本的先后顺序和在客户端执行脚本不一致,需要做个输出脚本等级的设计,在客户端要先执行的等级高一点,后执行的等级低一点,从而通过输出等级确定脚本内容各执行语句的输出顺序。
这里就有一个问题,这些js语句是什么时候生成的呢?答案是Render()。
MA的容器ajaxpanel派生于抽象基类RenderedByScriptControl,RenderedByScriptControl重载了Render方法 ,Render方法了里调用WriteScript()写出脚本,WriteScript()又调用了抽象方法RenderByScript(),RenderByScript方法则实际上由ajaxpanel来完成的,呵呵,典型的模版模式。由于控件是放在ajaxpanel里的,所以在输出子控件内容的阶段,ajaxpanel就把子控件的html标记放在一段<span>标记里,一旦子控件输出的内容有变化就只需要改变对应的span标记的innerHtml就可以了,而不用去关心控件输出的哪些html内容出现了变化。 我们可以把注意力可以完全集中在ajaxpanel的RenderByScript方法上了,我们来看看子控件内容的变化是如何被识别和记录的:
// If it's html rendering fingerprint is the same, ignore it. if (htmlFingerprint != (string)_controlHtmlFingerprints[con]) { ExtendedWriteSetHtmlOfElementScript(html, GetAjaxElemID(con)); _controlHtmlFingerprints[con] = htmlFingerprint; }
这段代码就是将ajaxpanel内部的控件的内容输出,并与上次页面的控件内容进行比较,如果有差异就进行脚本输出。以前的页面的控件内容是使用页面提交时传递过来的隐藏字段__CONTROL_FINGERPRINTS_AjaxPanel1的值来标识的。例如这样的值:"DD9A4F13;Button1#D8B12665;DataGrid1#7059016E;TextBox1#1505",就表示了button1,datagrid1,textbox1的值的hashcode值,可以配置为hashcode,md5,fullhtml等格式。这样通过比较控件内容的指纹就可以判断哪些要通过脚本语句改变值。
从上面的代码和分析中我们可以得出看出,nostore的ajax很好地维护了已有的asp.net编程模型,并没有像ajax.net一样破坏程序模型。而store模式的magicAjax则将webform的程序模型向winform史无前例地拉近了,也许,变革就在前方。
在store模型下,我们可以选择将页面实例对象储存在session和cache里,一旦有ajax请求,就从session或cache里获取对象,而不用再一次地去执行页面的一个生存周期,将临时页面对象换成了常驻内存的对象。
我们来看看这种模式下是如何获得体现改变的脚本代码的,以session储存方式为例。 在AcquireRequestState阶段,就不用像nostore那样调用HttpContext.Current.Handler.ProcessRequest来处理请求生成页面了,而是根据传回的pagekey从session中获取储存页面对象:
_magicAjaxContext.StoredPageInfo = GetStoredPageInfo(pageKey); 然后调用ProcessAjaxCall方法处理这个页面。在ProcessAjaxCall方法中,激发提交目标对象的服务端事件,比如一个按钮的click:
string target = _request.Form["__EVENTTARGET"]; string argument = _request.Form["__EVENTARGUMENT"]; RaisePostBackEventInChild (page, target, argument);
RaisePostBackEventInChild方法很简单,调用目标控件的RaisePostBackEvent,如果是按钮,则会激发click事件,如果我们有绑定处理click事件的方法就可以实现将datagrid绑定数据源之类的操作。RaisePostBackEvent这些以前由asp.net页面完成的工作都由magicajax来完成的。
protected void RaisePostBackEventInChild(Page page, string eventTarget, string eventArgument) { Control eventcontrol = page.FindControl(eventTarget);
if (eventcontrol is IPostBackEventHandler) ((IPostBackEventHandler)eventcontrol).RaisePostBackEvent(eventArgument); }
数据更新以后,又随着InvokeScriptWriters方法进入magicPanel的RenderByScript方法中,跟nostore模式一样,都要借助magicpanel来生成差异化的脚本代码,也是通过比较控件生成的html的内容指纹来进行是否有变化的鉴别。
框架的核心流程就是上面这些,为了使叙述清晰,我省略掉了一些东西,如果有兴趣可以自己看看代码,magicajax的注释写得很好,比较好理解。 那么,为什么说store模式的编程模型与winform接近呢?我这里借用一下叶子的这篇文章http://wj.cnblogs.com/archive/2005/12/29/307704.html中的一个示例:
private void Button1_Click(object sender, System.EventArgs e) { HyperLink link = new HyperLink(); link.Text = "HyperLink" + AjaxPanel1.Controls.Count; AjaxPanel1.Controls.Add (link); }
这段代码的在nostore和session模式下有着截然不同的效果,前者正如我们在普通asp.net里页面作的那样,panel里永远都只有一个HyperLink,而后者却会不断增加新的HyperLink到页面上,熟悉winform编程的朋友都知道这个是和窗体编程一样的效果,因为现在的webform和winform一样都在内存里维持着自己的状态,每次请求都是内容的修改而不是重新生成,比较前端和后端都有着相似的地方。
将桌面程序和web程序的编程模型统一起来几乎是很多开发人员的梦想,而现在,梦想突然变清晰了。这样好的东西有没有什么问题呢?当然有,从magicajax自身的角度讲,使用session储存很消耗服务器的资源,而且他的储存方式只支持InProc,如果能支持其他方式的储存相信会使其能真正走向成熟的应用,如果能将页面储存在客户端也许就将引爆web2.0的RIA革命。另外,让web开发人员回归winfrom的开发模型绝对是个挑战,遵循哪种编程模型肯定会让开发人员困惑不已,而现在并不一定完善成熟的版本可能导致的问题也让magicajax继续停留在实验室阶段。 我相信在可预见的将来,magixAjax及其思想将会影响未来的web开发,带来全新的开发模型。昙花一现还是革命的前奏,让我们拭目以待! 在此也感谢叶子wj.cnblogs.com给我的帮助,使我对magicajax有了更多的认识!
|