侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

编写轻量ajax组件01-与webform平台上的各种实现方式比较

前端  /  管理员 发布于 5年前   533

前言

  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();  }}

  页面类:

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组件,这是一个开源的组件,下一篇就通过源码了解这个组件,借鉴它的处理过程,并且分析它的优缺点。


  • 上一条:
    基于HTML5 Ajax文件上传进度条如何实现(jquery版本)
    下一条:
    AngularJS tab栏实现和mvc小案例实例详解
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客