编写轻量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,首先就要将<form runat="server"></form>标签去掉,这个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在前台生成代理脚本,用于发送和接收请求,所以需要<form runat="server">标签。
前台代码:
<body>
<form id="form1" runat="server">
<div>
<input type="button" value="获取回调结果" 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>
后台代码:
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();
}
}
页面类:
[AjaxMethod]
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组件,这是一个开源的组件,下一篇就通过源码了解这个组件,借鉴它的处理过程,并且分析它的优缺点。
编写轻量ajax组件01-对比webform平台上的各种实现方式的更多相关文章
- 编写轻量ajax组件02-AjaxPro浅析
前言 上一篇介绍了在webform平台实现ajax的一些方式,并且实现一个基类.这一篇我们来看一个开源的组件:ajaxpro.虽然这是一个比较老的组件,不过实现思想和源码还是值得我们学习的.通过上一篇 ...
- 编写轻量ajax组件03-实现(附源码)
前言 通过前两篇的介绍,我们知道要执行页面对象的方法,核心就是反射,是从请求获取参数并执行指定方法的过程.实际上这和asp.net mvc框架的核心思想很类似,它会解析url,从中获取controll ...
- vue 移动端轻量日期组件不依赖第三方库
Vue版移动端日期选择组件 1.优点:不需要依赖其他第三方库,灵活可配置: 不需要依赖第三方组件的vue日期移动端组件 小轮子 轻量可复用: https://github.com/BeckReed ...
- 关于对MyBatis.net框架的学习笔记( MyBatis.net是一款灵活性极大,sql由开发者自行在xml中编写, 轻量的ORM映射框架). 同时避免了sql硬编码到代码中不易维护的问题...
对于为什么要用ORM,为什么又要选择MyBatis.net,这个问题希望读者自行查找资料.这里直接贴出相关的调试笔记. 步骤1)下载与引用. http://code.google.com/p/myba ...
- Blazor Bootstrap 组件库 Toast 轻量弹窗组件介绍
轻量级 Toast 弹窗 DEMO https://www.blazor.zone/toasts 基础用法: 用户操作时,右下角给予适当的提示信息 <ToastBox class="d ...
- 一种简单,轻量,灵活的C#对象转Json对象的方案
简单,是因为只有一个类 轻量,是因为整个类代码只有300行 灵活,是因为扩展方式只需要继承重写某个方法即可 补充:修正无法处理可空值类型的bug 首先我将这个类称之为JsonBuilder,我希望它以 ...
- React Native常用组件在Android和IOS上的不同
React Native常用组件在Android和IOS上的不同 一.Text组件在两个平台上的不同表现 1.1 height与fontSize 1.1.1只指定font,不指定height 在这种情 ...
- Vue.js:轻量高效的前端组件化方案
转发一篇尤老师对vue.js的介绍,了解vue.js的来龙去脉.不过现在已经是2.0了,也有添加一些新的东西,当然有些东西也改了. Vue.js:轻量高效的前端组件化方案 Vue.js 是我在2014 ...
- 推荐一个简单、轻量、功能非常强大的C#/ASP.NET定时任务执行管理器组件–FluentScheduler定时器
在C#WINFORM或者是ASP.NET的WEB应用程序中,根据各种定时任务的需求,比如:每天的数据统计,每小时刷新系统缓存等等,这个时候我们得应用到定时器这个东东. .NET Framework有自 ...
随机推荐
- Mysql 中 show full processlist
processlist命令的输出结果显示了有哪些线程在运行,可以帮助识别出有问题的查询语句,两种方式使用这个命令. 1. 进入MySQL/bin目录下输入mysqladmin processlist; ...
- Grunt学习使用
原文地址:Grunt学习使用必看 grunt简介神马的不多说,到处一大堆. 我只说说我已经实现了的代码. 按照官方的教程 相信已经配置好了,接下来说 package.json 和 Gruntfile. ...
- Nginx反向代理部署指南
一.反向代理 我们都知道,80端口是web服务的默认端口,其他主机访问web服务器也是默认和80端口进行web交互,而一台服务器也只有一个80端口,这是约定俗成的标准. 我们来看下面两个场景: 1.服 ...
- shell脚本规划化模板
shell脚本规划化模板 Linux运维过程中,shell脚本是不可缺少的工具,但是每个运维人员编程的习惯都不一样,很多时候就是实现某个功能,写出来的脚本都是烂七八糟的.脚本必须规范化,应该从以后几个 ...
- SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
相信很多人进行数据存储时,会遇上如标题的异常错误. 其实也不算上一个错误. 当你的程序中有宣告一个字段的数据类型为DateTime时,但你又没有赋值给它,就进行存储时,它就会得到这样一个结果. 看看下 ...
- EC笔记:第3部分:15、对原始资源的访问
使用对象来管理资源,可以避免因个人疏忽带来的一些低级错误,但是不是每件事都是称心如意的. 一些函数依然使用原始的资源对象,那么我们就需要为这些函数提供一个接口,让他们可以获取到原始对象. 继续拿13节 ...
- java中动态代理的实现
动态代理的实现 使用的模式:代理模式. 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.类似租房的中介. 两种动态代理: (1)jdk动态代理,jdk动态代理是由Java内部的反射机制 ...
- jsPanel插件Option总结
jsPanel插件Option总结 学习jsPanel之余对相关的选项进行了总结,便于参考. # 选项名称 类别 简要说明 1 autoclose configuration 设置一个时间在毫秒后,面 ...
- 窗体作为控件嵌入panel
EyeView frm = new EyeView(); frm.TopLevel = false; frm.Parent = this.panel1; frm.FormBorderStyle = F ...
- 通过使用OpenVPN来构建一个VPN
首先我们需要简单熟悉一下OpenVPN和VPN概念,方便我们在使用OpenVPN构建VPN时的操作~ VPN概述 VPN,即虚拟专用网络,其功能是:在公用网络上建立专用网络,进行加密通讯.在企业网络 ...