リンク: MagicAjax(v0.2.1)源码分析 – 九度IT技术(综合)社区 :: 总坛.

MagicAjax(v0.2.1)源码分析
发布: 九度IT
注册: 2005-12-10
高级工程师
2006-01-03, 22:49                  
 
      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有了更多的认识!