這篇文章主要介紹了寫輕量級ajax元件01-與webform平台上的各種實現方式比較,需要的朋友可以參考下
前言
Asp.net WebForm 和Asp.net MVC(簡稱MVC) 都是基於Asp.net的web開發框架,兩者有很大的區別,其中一個就是MVC更加註重http本質,而WebForm試圖屏蔽http,為此提供了大量的伺服器控制項和ViewState機制,讓開發人員可以像開發Windows Form應用程式一樣,基於事件模型去程式設計。兩者各有優缺點和適用情景,但MVC現在是許多Asp.net開發者的首選。
WebForm是建立在Asp.net的基礎上的,Asp.net提供了足夠的擴展性,我們也可以利用這些在WebForm下編寫像MVC一樣的框架,這個有機會再寫。說到WebForm很多人就會聯想到伺服器控件(拖曳控件!!!),其實不然,我們也可以完全不使用伺服器控件,像MVC一樣關注html。 WebForm要拋棄伺服器控件,集中在html,首先就要將9da45565527026e0988f6215b7b6a235f5a47148e367a6035fd7a2faa965022e標籤去掉,這個runat server 的form 是其PostBack機制的基礎。既然我們要回歸到html css js,那麼意味著許多東西都要自己實現,例如處理Ajax請求。不像MVC那樣,WebForm開始的設計就將伺服器控制項當作主要組成部分,如果不使用它,那麼只能利用它的擴充性去實作。
本系列就是實作一個基於WebForm平台的輕量ajax元件,主要分為三個部分:
1. 介紹WebForm下各種實作方式。
2. 分析ajaxpro組件。
3. 寫出自己的ajax元件。
一、Ajax簡介
非同步允許我們在不刷新整個頁面的情況下,像伺服器請求或提交資料。對於複雜的頁面,為了請求一點資料而重載整個頁面顯然是很低效的,ajax就是為了解決這個問題的。 ajax的核心是XmlHttpRequest對象,透過該對象,以文字的形式向伺服器提交請求。 XmlHttpRequest2.0後,也支援提交二進位資料。
ajax安全:出於安全考慮,ajax受同源策略限制;也就是只能存取同一個域、同一個連接埠的請求,跨域請求會被拒絕。當然有時候需求需要跨域發送請求,常用的跨域處理方法有CORS(跨域資源共享)和JSONP(參數式JSON)。
ajax資料互動格式:雖然Ajax核心物件XmlHttpRequest有"XML"字眼,但客戶端與伺服器資料交換格式不限於xml,例如現在更多是使用json格式。
ajax 也是有缺點的。例如對搜尋引擎的支援不太好;有時候也會違背url資源定位的初衷。
二、Asp.net MVC 平台下使用ajax
在MVC裡,ajax呼叫後台方法非常方便,只需要指定Action的名稱即可。
前台程式碼:
<body> <h1>index</h1> <input type="button" value="GetData" onclick="getData()" /> <span id="result"></span> </body> <script type="text/javascript"> function getData() { $.get("GetData", function (data) { $("#result").text(data); }); } </script>
# 後台程式碼:
##
public class AjaxController : Controller { public ActionResult GetData() { if(Request.IsAjaxRequest()) { return Content("data"); } return View(); } }
#三、WebForm 平台下使用ajax
3.1 基於伺服器控制項套件或第三方元件
這是基於伺服器控制項的,例如ajax toolkit工具包,或像FineUI這樣的元件。 web前端始終是由html css js組成的,只不過如何去產生的問題。原生的我們可以自己寫,或是用一些前端插件;基於伺服器控制項的,都是在後台產生的,通常效率也低一點。伺服器元件會在前台產生一系列代理,本質還是一樣的,只不過控制項封裝了這個過程,不需要我們自己寫。基於控制項或第三方元件的模式,在某些管理系統還挺有用的,訪問量不是很大,可以快速開發。3.2 基於ICallbackEventHandler介面
.net 提供了ICallbackEventHandler接口,用於處理回調請求。此介面需要用ClientScriptManager在前台產生代理腳本,用於傳送和接收請求,所以需要9da45565527026e0988f6215b7b6a235標籤。 前台程式碼:<body> <form id="form1" runat="server"> <p> <input type="button" value="获取回调结果" onclick="callServer()" /> <span id="result" style="color:Red;"></span> </p> </form> </body> <script type="text/javascript"> function getCallbackResult(result){ document.getElementById("result").innerHTML = result; } </script># 後台程式碼:##
public partial class Test1 : System.Web.UI.Page, ICallbackEventHandler { protected void Page_Load(object sender, EventArgs e) { //客户端脚本Manager ClientScriptManager scriptMgr = this.ClientScript; //获取回调函数,getCallbackResult就是回调函数 string functionName = scriptMgr.GetCallbackEventReference(this, "", "getCallbackResult", ""); //发起请求的脚本,callServer就是点击按钮事件的执行函数 string scriptExecutor = "function callServer(){" + functionName + ";}"; //注册脚本 scriptMgr.RegisterClientScriptBlock(this.GetType(), "callServer", scriptExecutor, true); } //接口方法 public string GetCallbackResult() { return "callback result"; } //接口方法 public void RaiseCallbackEvent(string eventArgument) { } }#這種方式有以下缺點
: 1. 實作起來較複雜,每個頁面Load事件都要去註冊對應的腳本。
2. 前台會產生一個用於代理程式的腳本檔案。
3. 對於頁面互動複雜的,實現起來非常麻煩。
4. 雖然是回調,但此時頁面物件還是生成了。
3.3 使用一般處理程序
一般处理程序其实是一个实现了IHttpHandler接口类,与页面类一样,它也可以用于处理请求。一般处理程序通常不用于生成html,也没有复杂的事件机制,只有一个ProcessRequest入口用于处理请求。我们可以将ajax请求地址写成.ashx文件的路径,这样就可以处理了,而且效率比较高。
要输出文本内容只需要Response.Write(data)即可,例如,从数据库获取数据后,序列化为json格式字符串,然后输出。前面说到,一般处理程序不像页面一样原来生成html,如果要生成html,可以通过加载用户控件生成。如:
public void ProcessRequest(HttpContext context) { Page page = new Page(); Control control = page.LoadControl("~/PageOrAshx/UserInfo.ascx"); if (control != null) { StringWriter sw = new StringWriter(); HtmlTextWriter writer = new HtmlTextWriter(sw); control.RenderControl(writer); string html = sw.ToString(); context.Response.Write(html); } }
这种方式的优点是轻量、高效;缺点是对于交互多的需要定义许多ashx文件,加大了管理和维护成本。
3.4 页面基类
将处理ajax请求的方法定义在页面对象内,这样每个页面就可以专注处理本页面相关的请求了。这里有点需要注意。
1.如何知道这个请求是ajax请求?
通过请求X-Requested-With:XMLHttlRequest 可以判断,大部份浏览器的异步请求都会包含这个请求头;也可以通过自定义请求头实现,例如:AjaxFlag:XHR。
2.在哪里统一处理?
如果在每个页面类里判断和调用是很麻烦的,所以将这个处理过程转到一个页面基类里处理。
3.如何知道调用的是哪个方法?
通过传参或者定义在请求头都可以,例如:MethodName:GetData。
4.知道方法名称了,如何动态调用?
反射。
5.如何知道该方法可以被外部调用?
可以认为public类型的就可以被外部调用,也可以通过标记属性标记。
通过上面的分析,简单实现如下
页面基类:
public class PageBase : Page { public override void ProcessRequest(HttpContext context) { HttpRequest request = context.Request; if (string.Compare(request.Headers["AjaxFlag"],"AjaxFlag",0) == 0) { string methodName = request.Headers["MethodName"]; if (string.IsNullOrEmpty(methodName)) { EndRequest("MethodName标记不能为空!"); } Type type = this.GetType().BaseType; MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); if (info == null) { EndRequest("找不到合适的方法调用!"); } string data = info.Invoke(this, null) as string; EndRequest(data); } base.ProcessRequest(context); } private void EndRequest(string msg) { HttpResponse response = this.Context.Response; response.Write(msg); response.End(); } }
页面类:
public partial class Test1 : PageBase { protected void Page_Load(object sender, EventArgs e) { } public string GetData() { return "213"; } }
前台代码:
function getData(){ $.ajax({ headers:{"AjaxFlag":"XHR","MethodName":"GetData"}, success:function(data){ $("#result").text(data); } }); }
四、优化版页面基类
上面的页面基类功能很少,而且通过反射这样调用的效率很低。这里优化一下:
1.可以支持简单类型的参数。
例如上面的GetData可以是:GetData(string name),通过函数元数据可以获取相关的参数,再根据请求的参数,就可以设置参数了。
2.加入标记属性。
只有被AjaxMethodAttribute标记的属性才能被外部调用。
3.优化反射。
利用缓存,避免每次都根据函数名称去搜索函数信息。
标记属性:
public class AjaxMethodAttribute : Attribute { }
缓存对象:
public class CacheMethodInfo { public string MethodName { get; set; } public MethodInfo MethodInfo { get; set; } public ParameterInfo[] Parameters { get; set; } }
基类代码:
public class PageBase : Page { private static Hashtable _ajaxTable = Hashtable.Synchronized(new Hashtable()); public override void ProcessRequest(HttpContext context) { HttpRequest request = context.Request; if (string.Compare(request.Headers["AjaxFlag"],"XHR",true) == 0) { InvokeMethod(request.Headers["MethodName"]); } base.ProcessRequest(context); } /// <summary> /// 反射执行函数 /// </summary> /// <param name="methodName"></param> private void InvokeMethod(string methodName) { if (string.IsNullOrEmpty(methodName)) { EndRequest("MethodName标记不能为空!"); } CacheMethodInfo targetInfo = TryGetMethodInfo(methodName); if (targetInfo == null) { EndRequest("找不到合适的方法调用!"); } try { object[] parameters = GetParameters(targetInfo.Parameters); string data = targetInfo.MethodInfo.Invoke(this, parameters) as string; EndRequest(data); } catch (FormatException) { EndRequest("参数类型匹配发生错误!"); } catch (InvalidCastException) { EndRequest("参数类型转换发生错误!"); } catch (ThreadAbortException) { } catch (Exception e) { EndRequest(e.Message); } } /// <summary> /// 获取函数元数据并缓存 /// </summary> /// <param name="methodName"></param> /// <returns></returns> private CacheMethodInfo TryGetMethodInfo(string methodName) { Type type = this.GetType().BaseType; string cacheKey = type.AssemblyQualifiedName; Dictionary<string, CacheMethodInfo> dic = _ajaxTable[cacheKey] as Dictionary<string, CacheMethodInfo>; if (dic == null) { dic = new Dictionary<string, CacheMethodInfo>(); MethodInfo[] methodInfos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) let ma = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false) where ma.Length > 0 select m).ToArray(); foreach (var mi in methodInfos) { CacheMethodInfo cacheInfo = new CacheMethodInfo(); cacheInfo.MethodName = mi.Name; cacheInfo.MethodInfo = mi; cacheInfo.Parameters = mi.GetParameters(); dic.Add(mi.Name, cacheInfo); } _ajaxTable.Add(cacheKey, dic); } CacheMethodInfo targetInfo = null; dic.TryGetValue(methodName, out targetInfo); return targetInfo; } /// <summary> /// 获取函数参数 /// </summary> /// <param name="parameterInfos"></param> /// <returns></returns> private object[] GetParameters(ParameterInfo[] parameterInfos) { if (parameterInfos == null || parameterInfos.Length <= 0) { return null; } HttpRequest request = this.Context.Request; NameValueCollection nvc = null; string requestType = request.RequestType; if (string.Compare("GET", requestType, true) == 0) { nvc = request.QueryString; } else { nvc = request.Form; } int length = parameterInfos.Length; object[] parameters = new object[length]; if (nvc == null || nvc.Count <= 0) { return parameters; } for (int i = 0; i < length; i++) { ParameterInfo pi = parameterInfos[i]; string[] values = nvc.GetValues(pi.Name); object value = null; if (values != null) { if (values.Length > 1) { value = String.Join(",", values); } else { value = values[0]; } } if (value == null) { continue; } parameters[i] = Convert.ChangeType(value, pi.ParameterType); } return parameters; } private void EndRequest(string msg) { HttpResponse response = this.Context.Response; response.Write(msg); response.End(); } }
页面类:
public string GetData3(int i, double d, string str) { string[] datas = new string[] { i.ToString(), d.ToString(), str }; return "参数分别是:" + String.Join(",", datas); }
前台代码:
function getData3(){ $.ajax({ headers:{"AjaxFlag":"XHR","MethodName":"GetData3"}, data:{"i":1,"d":"10.1a","str":"hehe"}, success:function(data){ $("#result").text(data); } }); }
五、总结
上面的页面基类已经具备可以完成基本的功能,但它还不够好。主要有:
1. 依附在页面基类。对于本来有页面基类的,无疑会变得更加复杂。我们希望把它独立开来,变成一个单独的组件。
2. 效率问题。反射的效率是很低的,尤其在web这类应用程序上,更应该慎用。以动态执行函数为例,效率主要低在:a.根据字符串动态查找函数的过程。b.执行函数时,反射内部需要将参数打包成一个数组,再将参数解析到线程栈上;在调用前CLR还要检测参数的正确性,再判断有没有权限执行。上面的优化其实只优化了一半,也就是优化了查找的过程,而Invoke同样会有性能损失。当然,随着.net版本越高,反射的效率也会有所提升,但这种动态的东西,始终是用效率换取灵活性的。
3.不能支持复杂参数。有时候参数比较多,函数参数一般会封装成一个对象类型。
4. AjaxMethodAttribute只是一个空的标记属性。我们可以为它加入一些功能,例如,标记函数的名称、是否使用Session、缓存设置等都可以再这里完成。
用过WebForm的朋友可能会提到AjaxPro组件,这是一个开源的组件,下一篇就通过源码了解这个组件,借鉴它的处理过程,并且分析它的优缺点。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
淺析json與jsonp區別及透過ajax取得json資料後格式的轉換
#
以上是編寫輕量ajax元件01-與webform平台上的各種實作方式比較的詳細內容。更多資訊請關注PHP中文網其他相關文章!