前言
Asp.net WebForm 和 Asp.net MVC(簡稱MVC) 都是基于Asp.net的web開發(fā)框架,兩者有很大的區(qū)別,其中一個(gè)就是MVC更加注重http本質(zhì),而WebForm試圖屏蔽http,為此提供了大量的服務(wù)器控件和ViewState機(jī)制,讓開發(fā)人員可以像開發(fā)Windows Form應(yīng)用程序一樣,基于事件模型去編程。兩者各有優(yōu)缺點(diǎn)和適用情景,但MVC現(xiàn)在是許多Asp.net開發(fā)者的首選。
WebForm是建立在Asp.net的基礎(chǔ)上的,Asp.net提供了足夠的擴(kuò)展性,我們也可以利用這些在WebForm下編寫像MVC一樣的框架,這個(gè)有機(jī)會(huì)再寫。說到WebForm很多人就會(huì)聯(lián)想到服務(wù)器控件(拖控件?。。。?,其實(shí)不然,我們也可以完全不使用服務(wù)器控件,像MVC那樣關(guān)注html。WebForm要拋棄服務(wù)器控件,集中關(guān)注html,首先就要將form runat="server">/form>標(biāo)簽去掉,這個(gè)runat server 的form 是其PostBack機(jī)制的基礎(chǔ)。既然我們要回歸到html+css+js,那么意味著許多東西都要自己實(shí)現(xiàn),例如處理Ajax請(qǐng)求。不像MVC那樣,WebForm開始的設(shè)計(jì)就將服務(wù)器控件作為主要組成部分,如果不使用它,那么只能利用它的擴(kuò)展性去實(shí)現(xiàn)。
本系列就是實(shí)現(xiàn)一個(gè)基于WebForm平臺(tái)的輕量級(jí)ajax組件,主要分為三個(gè)部分:
1. 介紹WebForm下各種實(shí)現(xiàn)方式。
2. 分析ajaxpro組件。
3. 編寫自己的ajax組件。
一、Ajax簡介
異步允許我們?cè)诓凰⑿抡麄€(gè)頁面的情況下,像服務(wù)器請(qǐng)求或提交數(shù)據(jù)。對(duì)于復(fù)雜的頁面,為了請(qǐng)求一點(diǎn)數(shù)據(jù)而重載整個(gè)頁面顯然是很低效的,ajax就是為了解決這個(gè)問題的。ajax的核心是XmlHttpRequest對(duì)象,通過該對(duì)象,以文本的形式向服務(wù)器提交請(qǐng)求。XmlHttpRequest2.0后,還支持提交二進(jìn)制數(shù)據(jù)。
ajax安全:出于安全考慮,ajax受同源策略限制;也就是只能訪問同一個(gè)域、同一個(gè)端口的請(qǐng)求,跨域請(qǐng)求會(huì)被拒絕。當(dāng)然有時(shí)候需求需要跨域發(fā)送請(qǐng)求,常用的跨域處理方法有CORS(跨域資源共享)和JSONP(參數(shù)式JSON)。
ajax數(shù)據(jù)交互格式:雖然Ajax核心對(duì)象XmlHttpRequest有"XML"字眼,但客戶端與服務(wù)器數(shù)據(jù)交換格式不局限于xml,例如現(xiàn)在更多是使用json格式。
ajax 也是有缺點(diǎn)的。例如對(duì)搜索引擎的支持不太好;有時(shí)候也會(huì)違背url資源定位的初衷。
二、Asp.net MVC 平臺(tái)下使用ajax
在MVC里,ajax調(diào)用后臺(tái)方法非常方便,只需要指定Action的名稱即可。
前臺(tái)代碼:
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>
后臺(tái)代碼:
public class AjaxController : Controller
{
public ActionResult GetData()
{
if(Request.IsAjaxRequest())
{
return Content("data");
}
return View();
}
}
三、WebForm 平臺(tái)下使用ajax
3.1 基于服務(wù)器控件包或者第三方組件
這是基于服務(wù)器控件的,例如ajax toolkit工具包,或者像FineUI這樣的組件。web前端始終是由html+css+js組成的,只不過如何去生成的問題。原生的我們可以自己編寫,或者用一些前端插件;基于服務(wù)器控件的,都是在后臺(tái)生成的,通常效率也低一點(diǎn)。服務(wù)器組件會(huì)在前臺(tái)生成一系列代理,本質(zhì)還是一樣的,只不過控件封裝了這個(gè)過程,不需要我們自己編寫?;诳丶蛘叩谌浇M件的模式,在一些管理系統(tǒng)還是挺有用的,訪問量不是很大,可以快速開發(fā)。
3.2 基于ICallbackEventHandler接口
.net 提供了ICallbackEventHandler接口,用于處理回調(diào)請(qǐng)求。該接口需要用ClientScriptManager在前臺(tái)生成代理腳本,用于發(fā)送和接收請(qǐng)求,所以需要form runat="server">標(biāo)簽。
前臺(tái)代碼:
body>
form id="form1" runat="server">
div>
input type="button" value="獲取回調(diào)結(jié)果" onclick="callServer()" />
span id="result" style="color:Red;">/span>
/div>
/form>
/body>
script type="text/javascript">
function getCallbackResult(result){
document.getElementById("result").innerHTML = result;
}
/script>
后臺(tái)代碼:
public partial class Test1 : System.Web.UI.Page, ICallbackEventHandler
{
protected void Page_Load(object sender, EventArgs e)
{
//客戶端腳本Manager
ClientScriptManager scriptMgr = this.ClientScript;
//獲取回調(diào)函數(shù),getCallbackResult就是回調(diào)函數(shù)
string functionName = scriptMgr.GetCallbackEventReference(this, "", "getCallbackResult", "");
//發(fā)起請(qǐng)求的腳本,callServer就是點(diǎn)擊按鈕事件的執(zhí)行函數(shù)
string scriptExecutor = "function callServer(){" + functionName + ";}";
//注冊(cè)腳本
scriptMgr.RegisterClientScriptBlock(this.GetType(), "callServer", scriptExecutor, true);
}
//接口方法
public string GetCallbackResult()
{
return "callback result";
}
//接口方法
public void RaiseCallbackEvent(string eventArgument)
{
}
}
這種方式有以下缺點(diǎn):
1. 實(shí)現(xiàn)起來較復(fù)雜,每個(gè)頁面Load事件都要去注冊(cè)相應(yīng)的腳本。
2. 前臺(tái)會(huì)生成一個(gè)用于代理的腳本文件。
3. 對(duì)于頁面交互復(fù)雜的,實(shí)現(xiàn)起來非常麻煩。
4. 雖然是回調(diào),但是此時(shí)頁面對(duì)象還是生成了。
3.3 使用一般處理程序
一般處理程序其實(shí)是一個(gè)實(shí)現(xiàn)了IHttpHandler接口類,與頁面類一樣,它也可以用于處理請(qǐng)求。一般處理程序通常不用于生成html,也沒有復(fù)雜的事件機(jī)制,只有一個(gè)ProcessRequest入口用于處理請(qǐng)求。我們可以將ajax請(qǐng)求地址寫成.ashx文件的路徑,這樣就可以處理了,而且效率比較高。
要輸出文本內(nèi)容只需要Response.Write(data)即可,例如,從數(shù)據(jù)庫獲取數(shù)據(jù)后,序列化為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);
}
}
這種方式的優(yōu)點(diǎn)是輕量、高效;缺點(diǎn)是對(duì)于交互多的需要定義許多ashx文件,加大了管理和維護(hù)成本。
3.4 頁面基類
將處理ajax請(qǐng)求的方法定義在頁面對(duì)象內(nèi),這樣每個(gè)頁面就可以專注處理本頁面相關(guān)的請(qǐng)求了。這里有點(diǎn)需要注意。
1.如何知道這個(gè)請(qǐng)求是ajax請(qǐng)求?
通過請(qǐng)求X-Requested-With:XMLHttlRequest 可以判斷,大部份瀏覽器的異步請(qǐng)求都會(huì)包含這個(gè)請(qǐng)求頭;也可以通過自定義請(qǐng)求頭實(shí)現(xiàn),例如:AjaxFlag:XHR。
2.在哪里統(tǒng)一處理?
如果在每個(gè)頁面類里判斷和調(diào)用是很麻煩的,所以將這個(gè)處理過程轉(zhuǎn)到一個(gè)頁面基類里處理。
3.如何知道調(diào)用的是哪個(gè)方法?
通過傳參或者定義在請(qǐng)求頭都可以,例如:MethodName:GetData。
4.知道方法名稱了,如何動(dòng)態(tài)調(diào)用?
反射。
5.如何知道該方法可以被外部調(diào)用?
可以認(rèn)為public類型的就可以被外部調(diào)用,也可以通過標(biāo)記屬性標(biāo)記。
通過上面的分析,簡單實(shí)現(xiàn)如下
頁面基類:
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標(biāo)記不能為空!");
}
Type type = this.GetType().BaseType;
MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
if (info == null)
{
EndRequest("找不到合適的方法調(diào)用!");
}
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";
}
}
前臺(tái)代碼:
function getData(){
$.ajax({
headers:{"AjaxFlag":"XHR","MethodName":"GetData"},
success:function(data){
$("#result").text(data);
}
});
}
四、優(yōu)化版頁面基類
上面的頁面基類功能很少,而且通過反射這樣調(diào)用的效率很低。這里優(yōu)化一下:
1.可以支持簡單類型的參數(shù)。
例如上面的GetData可以是:GetData(string name),通過函數(shù)元數(shù)據(jù)可以獲取相關(guān)的參數(shù),再根據(jù)請(qǐng)求的參數(shù),就可以設(shè)置參數(shù)了。
2.加入標(biāo)記屬性。
只有被AjaxMethodAttribute標(biāo)記的屬性才能被外部調(diào)用。
3.優(yōu)化反射。
利用緩存,避免每次都根據(jù)函數(shù)名稱去搜索函數(shù)信息。
標(biāo)記屬性:
public class AjaxMethodAttribute : Attribute
{
}
緩存對(duì)象:
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>
/// 反射執(zhí)行函數(shù)
/// /summary>
/// param name="methodName">/param>
private void InvokeMethod(string methodName)
{
if (string.IsNullOrEmpty(methodName))
{
EndRequest("MethodName標(biāo)記不能為空!");
}
CacheMethodInfo targetInfo = TryGetMethodInfo(methodName);
if (targetInfo == null)
{
EndRequest("找不到合適的方法調(diào)用!");
}
try
{
object[] parameters = GetParameters(targetInfo.Parameters);
string data = targetInfo.MethodInfo.Invoke(this, parameters) as string;
EndRequest(data);
}
catch (FormatException)
{
EndRequest("參數(shù)類型匹配發(fā)生錯(cuò)誤!");
}
catch (InvalidCastException)
{
EndRequest("參數(shù)類型轉(zhuǎn)換發(fā)生錯(cuò)誤!");
}
catch (ThreadAbortException)
{
}
catch (Exception e)
{
EndRequest(e.Message);
}
}
/// summary>
/// 獲取函數(shù)元數(shù)據(jù)并緩存
/// /summary>
/// param name="methodName">/param>
/// returns>/returns>
private CacheMethodInfo TryGetMethodInfo(string methodName)
{
Type type = this.GetType().BaseType;
string cacheKey = type.AssemblyQualifiedName;
Dictionarystring, CacheMethodInfo> dic = _ajaxTable[cacheKey] as Dictionarystring, CacheMethodInfo>;
if (dic == null)
{
dic = new Dictionarystring, 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>
/// 獲取函數(shù)參數(shù)
/// /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 "參數(shù)分別是:" + String.Join(",", datas);
}
前臺(tái)代碼:
function getData3(){
$.ajax({
headers:{"AjaxFlag":"XHR","MethodName":"GetData3"},
data:{"i":1,"d":"10.1a","str":"hehe"},
success:function(data){
$("#result").text(data);
}
});
}
五、總結(jié)
上面的頁面基類已經(jīng)具備可以完成基本的功能,但它還不夠好。主要有:
1. 依附在頁面基類。對(duì)于本來有頁面基類的,無疑會(huì)變得更加復(fù)雜。我們希望把它獨(dú)立開來,變成一個(gè)單獨(dú)的組件。
2. 效率問題。反射的效率是很低的,尤其在web這類應(yīng)用程序上,更應(yīng)該慎用。以動(dòng)態(tài)執(zhí)行函數(shù)為例,效率主要低在:a.根據(jù)字符串動(dòng)態(tài)查找函數(shù)的過程。b.執(zhí)行函數(shù)時(shí),反射內(nèi)部需要將參數(shù)打包成一個(gè)數(shù)組,再將參數(shù)解析到線程棧上;在調(diào)用前CLR還要檢測(cè)參數(shù)的正確性,再判斷有沒有權(quán)限執(zhí)行。上面的優(yōu)化其實(shí)只優(yōu)化了一半,也就是優(yōu)化了查找的過程,而Invoke同樣會(huì)有性能損失。當(dāng)然,隨著.net版本越高,反射的效率也會(huì)有所提升,但這種動(dòng)態(tài)的東西,始終是用效率換取靈活性的。
3.不能支持復(fù)雜參數(shù)。有時(shí)候參數(shù)比較多,函數(shù)參數(shù)一般會(huì)封裝成一個(gè)對(duì)象類型。
4. AjaxMethodAttribute只是一個(gè)空的標(biāo)記屬性。我們可以為它加入一些功能,例如,標(biāo)記函數(shù)的名稱、是否使用Session、緩存設(shè)置等都可以再這里完成。
用過WebForm的朋友可能會(huì)提到AjaxPro組件,這是一個(gè)開源的組件,下一篇就通過源碼了解這個(gè)組件,借鑒它的處理過程,并且分析它的優(yōu)缺點(diǎn)。
您可能感興趣的文章:- Ajax核心XMLHTTP組件資料
- 編寫輕量ajax組件02--淺析AjaxPro
- 編寫輕量ajax組件第三篇實(shí)現(xiàn)