Ajax框架原理分析之Ext.Net

  Ext.Net也是一个很有名的前端框架,它构建于ExtJS之上,提供了一整套UI+AJAX的解决方案.通过对其源码的研究,了解其是如何实现这套AJAX的,对我们也是很有帮助的.

  1.静态的AJAX方法实现.

  当把[DirectMethod]标记标在一个静态方法上时,Ext.Net是通过HttpModule来截获Http请求实现的.具体的实现类为:DirectRequestModule类

  在应用程序生命周期的PostAcquireRequestState事件内中加入处理函数


public void Init(HttpApplication app)
{
    app.PostAcquireRequestState 
+= OnPostAcquireRequestState;
    app.PreSendRequestHeaders 
+= RedirectPreSendRequestHeaders;
}


  如果是AJAX请求且是静态AJAX方法则调用ProcessRequest方法

代码
private void OnPostAcquireRequestState(object sender, EventArgs eventArgs)
{
    HttpApplication app 
= (HttpApplication)sender;
    HttpRequest request 
= app.Context.Request;

    
if (RequestManager.IsAjaxRequest)
    {
        
if (DirectMethod.IsStaticMethodRequest(request) /*|| Utilities.ReflectionUtils.IsTypeOf(app.Context.Handler, "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapper")*/)
        {
            
this.ProcessRequest(app, request);
        }
    }
}


   通过反射来调用方法,并结束服务器处理过程,将结果返回到客户端(有删节)

代码
// Get handler
HandlerMethods handler = HandlerMethods.GetHandlerMethods(context, request.FilePath);


// Get method name to invoke
string methodName = HandlerMethods.GetMethodName(context);

DirectMethod directMethod 
= handler.GetStaticMethod(methodName);

object result = directMethod.Invoke();

app.Context.Response.Clear();
app.Context.Response.ClearContent();
app.Context.Response.ClearHeaders();
app.Context.Response.StatusCode 
= 200;
app.Context.Response.ContentType 
= "application/json";
app.Context.Response.Charset 
= "utf-8";
app.Context.Response.Cache.SetNoServerCaching();
app.Context.Response.Cache.SetMaxAge(TimeSpan.Zero);
app.Context.Response.Write(responseObject.ToString());
app.CompleteRequest();


  因为是直接通过反射来实现,没有执行页面的生命周期,所以Ext.Net官方推荐此种编写方式.

 

  2.实例的AJAX方法实现.

  我们要重点关注ResourceManager类,它是整个AJAX请求的核心

  首先我们来看是如何实现AJAX方法的调动.

  当客户端发起一个AJAX请求时,Ext.Net会在POST数据中加入以下两个键值对:

  __EVENTARGUMENT:btnOK|event|Click

  __EVENTVALIDATION:ResourceManager1

  表示由ResourceManager类型的ResourceManager1实例来处理这次AJAX请求.具体发起请求的按钮是btnOK,形式为事件,方式为Click.

  在ResourceManager类的OnLoad事件中,如果是AJAX请求,则在LoadComplete事件中加入Page_AjaxLoadComplete处理函数

代码
protected override void OnLoad(EventArgs e)
{
    
base.OnLoad(e);

    
if (RequestManager.IsAjaxRequest && !this.Page.IsPostBack && !this.IsDynamic)
    {
        
this.Page.LoadComplete += Page_AjaxLoadComplete;
    }
}


  在Page_AjaxLoadComplete处理函数中,借用回发的处理方法来实现AJAX调用:

代码
string _ea = this.Page.Request["__EVENTARGUMENT"];

if (_ea.IsNotEmpty())
{
    
string _et = this.Page.Request["__EVENTTARGET"];

    
if (_et == this.UniqueID)
    {
        
this.RaisePostBackEvent(_ea);
    }

    
return;
}


  在RaisePostBackEvent方法中,实现对具体控件具体方法的具体调用:

代码
string controlID = args[0];
string controlEvent = args[2];

ctrl 
= ControlUtils.FindControlByClientID(this.Page, controlID, truenull);

case AjaxRequestType.Event:
    Observable observable 
= ctrl as Observable;

    
if (observable == null)
    {
        
if (ctrl is ResourceManagerProxy)
        {
            ((ResourceManagerProxy)ctrl).FireAsyncEvent(controlEvent, extraParams);
        }
        
else if (ctrl is ResourceManager)
        {
            
this.FireAsyncEvent(controlEvent, extraParams);
        }
        
else
        {
            
throw new HttpException("The control with ID '{0}' is not Observable".FormatWith(controlID));
        }
    }
    
    
if (observable != null)
    {
        observable.FireAsyncEvent(controlEvent, extraParams);
    }
    
break;


  然后我们来看Ext.Net是如何将处理结果返回给客户端的.

  (29日继续)

  首先我们要明白一点,后台的Ext.Net控件在输出到客户端的并不是HTML代码,而是JSON包装后的JS代码。浏览器执行接收到的JS代码后再生成HTML代码。

  我们知道,控件的输出一般都写在生命周期的Render事件中。XControl是所有Ext.Net控件的基类。这个类比较大,作者用了近十个文件,采用分部类的开发方式来实现这个类。Lifecycle.cs文件主要负责重写控件的生命周期。在其重写的Render中,调用了HtmlRender方法。此方法是一般的具体控件的实际输出方法。另外在ResourceManager类的RenderAction方法中输出页面脚本注册,样式注册,页面初始化脚本等等。这些输出不是单纯的输出HTML或JS代码,而是在输出的内容的两端加上了类似于“<Ext.Net.Direct.Response>”的标签,这是为下一步输出过滤做准备。

  接着就要看输出过滤了。我们知道,传统的Asp.Net提交,服务器会完成整个页面的生命周期,之后将处理过后的整个页面的内容返回。但是Ext.Net的AJAX提交走完了整个页面的生命周期,返回的却是Json数据。这其中倒底有什么玄机?答案就在输出过滤!(光寻找这一点我就花了近三个小时,原因是我在听WebCast的的时候,老赵说Asp.net Ajax框架是在Render事件上做手脚,对内容进行了重输出,我想Ext.Net应该差不多,于是使劲找,结果什么也找不到。  —_—!)

  

  还是在DirectRequestModule类中:

public void Init(HttpApplication app)
{
    app.ReleaseRequestState += AjaxRequestFilter;
}

 

  一开始我以为没什么用,现在才知道这是处理AJAX返回数据的关键!

代码
HttpResponse response = HttpContext.Current.Response;

if (RequestManager.IsAjaxRequest)
{
    
if (response.ContentType.IsNotEmpty() && response.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
    {
        response.Filter = new AjaxRequestFilter(response.Filter);
    }
}


  在这里,AjaxRequestFilter类是主要实现类。其Flush方法是关键方法。 里面用到的DirectResponse类是返回的包装类,将可能的返回信息封装成了一个类,我们一步一步的看。

 

string raw = this.html.ToString();

StringBuilder buffer 
= new StringBuilder(256);

DirectResponse ajaxResponse 
= new DirectResponse(true);
HttpContext context 
= HttpContext.Current;

这里是一些初始化的工作。其中html就是待过滤的原始的html代码。

 

object isUpdate = context.Items["Ext.Net.Direct.Update"];

if (isUpdate != null && (bool)isUpdate)
{
    
this.ExtractUpdates(raw, ref buffer);
}

这里是从原始html提取更新的部份并写到buffer中去。思路是用正则去匹配的,具体实现自行看代码!

 

代码
string dynamicHtml = this.ExtractDynamicHtml(raw);

object isManual = context.Items["Ext.Net.Direct.Response.Manual"];

if (isManual != null && (bool)isManual)
{
    
if (raw.StartsWith("<Ext.Net.Direct.Response.Manual>"))
    {
        
string script = dynamicHtml.ConcatWith(raw.RightOf("<Ext.Net.Direct.Response.Manual>").LeftOf("</Ext.Net.Direct.Response.Manual>"));
        
byte[] rsp = System.Text.Encoding.UTF8.GetBytes(script);
        
this.response.Write(rsp, 0, rsp.Length);
        
this.response.Flush();
        
return;
    }
}

buffer.Append(dynamicHtml);

这里是从原始的html提取动态生成的html代码,这里有个小插曲,如果isManual与raw满意要求的话,就直接将提取的结果返回了。否则也将结果写到buffer中。

 

代码
string error = context == null ? null : (context.Error != null ? context.Error.ToString() : null);

if (!ResourceManager.AjaxSuccess || error.IsNotEmpty())
{
    ajaxResponse.Success 
= false;

    
if (error.IsNotEmpty())
    {
        ajaxResponse.ErrorMessage 
= error; 
    }
    
else
    {
        ajaxResponse.ErrorMessage 
= ResourceManager.AjaxErrorMessage; 
    }
}

这里是错误处理,就不多说了。

 

代码
if (ResourceManager.ReturnViewState)
                {
                    ajaxResponse.ViewState 
= AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATE);
                    ajaxResponse.ViewStateEncrypted 
= AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATEENCRYPTED);
                    ajaxResponse.EventValidation 
= AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.EVENTVALIDATION);
                }

这里是从原始的html提取ViewState

 

代码
object obj = ResourceManager.ServiceResponse;

if (obj is Response)
{
    ajaxResponse.ServiceResponse 
= new ClientConfig().Serialize(obj);
}
else
{
    ajaxResponse.ServiceResponse 
= obj != null ? JSON.Serialize(obj) : null;
}

if (ResourceManager.ExtraParamsResponse.Count > 0)
{
    ajaxResponse.ExtraParamsResponse 
= ResourceManager.ExtraParamsResponse.ToJson();
}

if (ResourceManager.DirectMethodResult != null)
{
    ajaxResponse.Result 
= ResourceManager.DirectMethodResult;
}

这里是从ResourceManager类中获取指定数据。

 

buffer.Append(raw.RightOf("<Ext.Net.Direct.Response>").LeftOf("</Ext.Net.Direct.Response>"));

if (buffer.Length > 0)
{
    ajaxResponse.Script 
= "<string>".ConcatWith(buffer.ToString());
}

这里是从原始的html提取被"<Ext.Net.Direct.Response>"标记的内容。写到buffer中,并终赋到ajaxResponse.Script属性中。

 

代码
byte[] data = System.Text.Encoding.UTF8.GetBytes((isUpload ? "<textarea>" : ""+ ajaxResponse.ToString() + (isUpload ? "</textarea>" : "") );
this.response.Write(data, 0, data.Length);

this.response.Flush();

最后,将结果通过DirectResponse类重写过的ToString方法将结果序列化输出到客户端。


  总结输出的过程,其实是从原始的HTML代码提取出相关信息填充到DirectResponse类的相关属性中,再将其序列化到客户端的过程。从ResourceManager获取ServiceResponse,ExtraParamsResponse,Result,从原始HTML获取ViewState,ViewStateEncrypted,EventValidation,从原始HTML获取更新的数据,动态的HTML内容,"<Ext.Net.Direct.Response>"并填入Script属性,跟据需要设置Success与ErrorMessage属性。通过重写DirectResponse类的ToString方法,来实现序列化过程。

  以上就是我的分析。可能非常的粗枝大叶,但整个流程的基本架构是分析出来了。 虽然代码具体实现的好坏可能仁者见仁,智者见智,但里面真的还是有很多东西值得我去学习的!

posted @ 2010-11-26 10:55  永远的阿哲  阅读(2577)  评论(4编辑  收藏  举报