Ajax框架原理分析之Ext.Net
Ext.Net也是一个很有名的前端框架,它构建于ExtJS之上,提供了一整套UI+AJAX的解决方案.通过对其源码的研究,了解其是如何实现这套AJAX的,对我们也是很有帮助的.
1.静态的AJAX方法实现.
当把[DirectMethod]标记标在一个静态方法上时,Ext.Net是通过HttpModule来截获Http请求实现的.具体的实现类为:DirectRequestModule类
在应用程序生命周期的PostAcquireRequestState事件内中加入处理函数
{
app.PostAcquireRequestState += OnPostAcquireRequestState;
app.PreSendRequestHeaders += RedirectPreSendRequestHeaders;
}
如果是AJAX请求且是静态AJAX方法则调用ProcessRequest方法
{
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);
}
}
}
通过反射来调用方法,并结束服务器处理过程,将结果返回到客户端(有删节)
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处理函数
{
base.OnLoad(e);
if (RequestManager.IsAjaxRequest && !this.Page.IsPostBack && !this.IsDynamic)
{
this.Page.LoadComplete += Page_AjaxLoadComplete;
}
}
在Page_AjaxLoadComplete处理函数中,借用回发的处理方法来实现AJAX调用:
if (_ea.IsNotEmpty())
{
string _et = this.Page.Request["__EVENTTARGET"];
if (_et == this.UniqueID)
{
this.RaisePostBackEvent(_ea);
}
return;
}
在RaisePostBackEvent方法中,实现对具体控件具体方法的具体调用:
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类中:
{
app.ReleaseRequestState += AjaxRequestFilter;
}
一开始我以为没什么用,现在才知道这是处理AJAX返回数据的关键!
if (RequestManager.IsAjaxRequest)
{
if (response.ContentType.IsNotEmpty() && response.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
{
response.Filter = new AjaxRequestFilter(response.Filter);
}
}
在这里,AjaxRequestFilter类是主要实现类。其Flush方法是关键方法。 里面用到的DirectResponse类是返回的包装类,将可能的返回信息封装成了一个类,我们一步一步的看。
StringBuilder buffer = new StringBuilder(256);
DirectResponse ajaxResponse = new DirectResponse(true);
HttpContext context = HttpContext.Current;
这里是一些初始化的工作。其中html就是待过滤的原始的html代码。
if (isUpdate != null && (bool)isUpdate)
{
this.ExtractUpdates(raw, ref buffer);
}
这里是从原始html提取更新的部份并写到buffer中去。思路是用正则去匹配的,具体实现自行看代码!
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中。
if (!ResourceManager.AjaxSuccess || error.IsNotEmpty())
{
ajaxResponse.Success = false;
if (error.IsNotEmpty())
{
ajaxResponse.ErrorMessage = error;
}
else
{
ajaxResponse.ErrorMessage = ResourceManager.AjaxErrorMessage;
}
}
这里是错误处理,就不多说了。
{
ajaxResponse.ViewState = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATE);
ajaxResponse.ViewStateEncrypted = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.VIEWSTATEENCRYPTED);
ajaxResponse.EventValidation = AjaxRequestFilter.GetHiddenInputValue(raw, BaseFilter.EVENTVALIDATION);
}
这里是从原始的html提取ViewState
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类中获取指定数据。
if (buffer.Length > 0)
{
ajaxResponse.Script = "<string>".ConcatWith(buffer.ToString());
}
这里是从原始的html提取被"<Ext.Net.Direct.Response>"标记的内容。写到buffer中,并终赋到ajaxResponse.Script属性中。
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方法,来实现序列化过程。
以上就是我的分析。可能非常的粗枝大叶,但整个流程的基本架构是分析出来了。 虽然代码具体实现的好坏可能仁者见仁,智者见智,但里面真的还是有很多东西值得我去学习的!