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,  true null );

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方法,来实现序列化过程。

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

你可能感兴趣的:(ajax框架)