编写轻量ajax组件02-AjaxPro浅析

前言

  上一篇介绍了在webform平台实现ajax的一些方式,并且实现一个基类。这一篇我们来看一个开源的组件:ajaxpro。虽然这是一个比较老的组件,不过实现思想和源码还是值得我们学习的。通过上一篇的介绍,我们知道要调用页面对象的方法,就是靠反射来实现的,关键是整个处理过程,包括反射调用方法、参数映射等。ajaxpro不仅在后台帮我们实现了这个过程,在前台也封装了请求调用的方法,例如ajax的相关方法,用ajaxpro的方法就可以发送异步请求了,不需要自己封装js或者使用js库。接下来就对这个组件进行浅析。

一、ajaxpro的使用

  我们先来看这个组件如何使用。

  1. 注册AjaxHandlerFactory

  在web.config里进行如下配置:


1

2

3

<httpHandlers>

  <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>

</httpHandlers>

  简单的说,请求的url符合 ajaxpro/*.ashx 格式的,都会被AjaxHandlerFactory处理,这是一个实现IHandlerFactory接口的工厂类,用来获取IHandler处理程序。其中type的格式是:"名称控件.类名称,程序集名称"。

  2. 在页面类Page_Load事件进行注册


1

2

3

4

protected void Page_Load(object sender, EventArgs e)

{

    AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage));

}

  我们传递了本页面对象的Type给ResisterTypoForAjax方法,这个方法用来在前台注册脚本,具体会调用当前Page对象的RegisterClientScriptBlock进行注册,所以.aspx文件中必须有一个<form runat="server"></form>,否则脚本将无法注册。(这里传递了Type,实际也可以做到不用传递的,内部通过HttpContext.Current.Handler.GetType().BaseType 也可以获得这个类型)

  3.用AjaxMethod标记方法  


1

2

3

4

5

[AjaxMethod]

public List<string> GetList(string input1,string input2)

{

    return new List<string> { input1, input2 };

}

  AjaxMethod是一个标记属性,表示这个方法用于处理ajax请求,它最终通过反射执行;它有几个构造函数对,对于有些需要缓存的数据,可以设置缓存时间;如果我们的请求不需要使用Session,可以设置HttpSessionStateRequirement;如果请求需要异步,例如请求一个耗时的web服务,也可以设置处理程序为异步状态。

  方法的返回值可以是简单的类型,也可以是复杂的类型;例如集合类型在前台获得就是一个数组。

  4.前台调用

  后台的配置和使用都非常简单,接下来我们看前台如何发起请求。


1

2

3

4

5

6

7

function GetList() {

    //var result = AjaxProNamespace.AjaxProPage.GetList("a", "b").value;

    //console.log(result);

    AjaxProNamespace.AjaxProPage.GetList("a""b", function (result) {

        console.log(result);

    });      

}

  这里AjaxProNamespace 是页面类所在的名称空间,AjaxProPage 就是页面类的名称,GetList是标记的方法。为什么可以这样写呢?前面说到,ajaxpro会在前台注册脚本,它会根据我们页面对象的相关信息生成如下脚本,所以我们才可以这样调用,而完全不用自己写js或者用jquery库的方法。


1

2

3

4

5

6

7

8

9

10

if(typeof AjaxProNamespace == "undefined") AjaxProNamespace={};

if(typeof AjaxProNamespace.AjaxProPage_class == "undefined") AjaxProNamespace.AjaxProPage_class={};

AjaxProNamespace.AjaxProPage_class = function() {};

Object.extend(AjaxProNamespace.AjaxProPage_class.prototype, Object.extend(new AjaxPro.AjaxClass(), {

    GetList: function(input1, input2) {

        return this.invoke("GetList", {"input1":input1, "input2":input2}, this.GetList.getArguments().slice(2));

    },

    url: ‘/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx‘

}));

AjaxProNamespace.AjaxProPage = new AjaxProNamespace.AjaxProPage_class();

  GetList的参数对应后台方法的参数,类型必须可以转换,否则调用会失败。最后一个参数为回调函数,回调函数的参数是对返回结果进行封装的对象,其value属性就是执行成功返回的值,如上面返回的就是一个数组对象。其error包括了失败的信息。

  注意,上面注释掉的部分是同步请求的做法,这往往不是我们想要的,我曾经就见过有人这样错误的使用。

二、ajaxpro处理请求原理

  这里主要关注组件处理ajax请求的过程,其它辅助功能不做介绍。

  1.生成辅助脚本

  在Page_Load事件里我们调用了AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxProPage)); 用来注册所需要的脚本。我们注意到在前台页面引入了如下脚本:

也就是每个页面都会都会发起这几个Get 请求。这几个都是.ashx结尾的文件,但实际里面都是js代码;这些js有的是作为资源嵌套在dll内部,有的是自动生成的,主要是封装了ajax请求相关方法,以及让我们可以用:名称空间.页面类名称.标记方法名称 这样去调用方法。为什么要用.ashx而不是用.js呢?因为作为组件内部的资源文件,外部无法直接请求.js文件,而.ashx可以被拦截,然后用Response.Write将内容输出。

  如果每次都生成和发送这些脚本的效率是很低的,ajaxpro内部的处理是判断请求头的If-None-Math和If-Modified-Since,如果两个都和缓存的一样,就返回一个304状态码。所以,客户端只有首次请求服务端会返回文件的内容,后续的都只返回304表示使用本地缓存。我们刷新页面可以验证这个过程:

  我们知道304状态码表示服务端告诉浏览器可以使用本地缓存,它的具体过程是这样的:浏览器将发送请求,Request包括If-None-Math和If-Modified-Since;服务端接收到请求后,判断If-None-Math和ETag是否一样,判断If-Modified-Since和请求内容的Last-Modified-Time是否一样;如果都一样,则返回304状态码,浏览器接收到304,就直接使用本地缓存;如果有一个不一样,服务端都将输出具体内容,此时Response包含新的ETag和Last-Modified-Time。这个过程最明显的好处就是服务端不需要发送内容给浏览器,但缺点就是浏览器和服务端还需要一次请求-响应的过程。个人认为这里可以使用Cache-Control,并设置一个较大值的时间,因为这里的js文件内容基本是不会变化的。Cache-Control表示浏览器请求时,先判断请求是否过时,如果没有过时,则直接从本地缓存获得,这个过程浏览器不需要和服务端建立任何请求;如果过时,浏览器才会发起请求。(需要注意的是,浏览器缓存都是基于Get请求的,Post请求是不会被缓存的)

  2. 拦截请求

  HttpHandler(IHttpHandler) 和 HttpModule(IHttpModule) 是asp.net 两个重要的组件,让我们可以在asp.net的基础上很方便的进行扩展。HttpHandler对应某种具体的请求,例如.ashx,.aspx等;HttpModule是一个拦截器,可以在管道的某个事件对所有请求进行拦截。简单的说,在管道中,HttpApplication会触发一系列事件,我们在通过HttpModule对某个事件进行注册,例如我们可以在处理程序对象生成前拦截请求,然后映射到自己的处理程序;而实际处理请求返回结果的是HttpHandler,例如Page用来生成html。

  以asp.net mvc框架为例,它是建立在asp.net 路由机制的基础上的,asp.net 路由系统通过一个UrlRoutingModule对请求进行拦截,具体是在PostResolveRequestCache事件进行拦截,对url进行解析,封装相应的路由数据后,最终将请求交给一个MvcHandler进行处理,MvcHandler实现了IHttpHandler接口。

  前面我们进行了如下配置:<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/> 这表明了任何的以 ajaxpro/任意名称.ashx结尾的 Post/Get 请求,都交给AjaxPro.AjaxHandlerFactory进行处理,它是一个实现了IHandlerFactory的处理程序工厂,用来生成具体的IHttpHandler。组件内部定义了多个实现IHttpHandler的类,有的是为了生成js脚本的,对于处理ajax请求,主要分为两类:异步(IHttpAsyncHandler)和非异步(IHttpHandler);在这两类的基础上,对于Session的状态的支持又分为三种:支持读写(实现IRequiresSessionState标记接口)的Handler、只读(实现IReadOnlySessionState标记接口)的Handler和不支持Session的Handler。具体生成什么样的Handler是通过AjaxMethod进行判断的。

  IHttpHandler的ProcessRequest(异步就是BeginProcessRequest)就用来执行请求返回输出结果的。如果只需要一种处理程序我们也可以实现IHttpHandler。IHandlerFactory的定义如下:


1

2

3

4

5

public interface IHttpHandlerFactory

{

    IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);

    void ReleaseHandler(IHttpHandler handler);

}  

  所以,ajaxpro的所有请求都会符合ajaxpro/*.ashx格式,然后在GetHandler方法,就可以进行具体的处理,返回结果是IHttpHandler;以非异步状态为例,如果我们配置了需要Session,就会生成一个实现IHttpHandler和IRequiresSessionState的Handler,如果需要只读的Session,就会生成一个实现IHttpHandler和IReadOnlySessionState的Handler;这些信息可以通过反射从AjaxMethod标记属性获得。AjaxHandlerFactory的主要代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)

{

    string filename = Path.GetFileNameWithoutExtension(context.Request.Path);

    Type t = null;

    Exception typeException = null;

    bool isInTypesList = false;

    switch (requestType)

    {

        //Get请求,获取前面的那4个脚本

        case "GET":    

            switch (filename.ToLower())

            {

                case "prototype":

                    return new EmbeddedJavaScriptHandler("prototype");

                case "core":

                    return new EmbeddedJavaScriptHandler("core");

                case "ms":

                    return new EmbeddedJavaScriptHandler("ms");

                case "prototype-core":

                case "core-prototype":

                    return new EmbeddedJavaScriptHandler("prototype,core");

                case "converter":

                    return new ConverterJavaScriptHandler();

                default:

                    return new TypeJavaScriptHandler(t);

            }

        case "POST":

            IAjaxProcessor[] p = new IAjaxProcessor[2];

            p[0] = new XmlHttpRequestProcessor(context, t);

            p[1] = new IFrameProcessor(context, t);

            for (int i = 0; i < p.Length; i++)

            {

                if (p[i].CanHandleRequest)

                {

                    //获取标记方法的AjaxMethod属性

                    AjaxMethodAttribute[] ma = (AjaxMethodAttribute[])p[i].AjaxMethod.GetCustomAttributes(typeof(AjaxMethodAttribute), true);

                    bool useAsync = false;

                    HttpSessionStateRequirement sessionReq = HttpSessionStateRequirement.ReadWrite;

                    if (ma.Length > 0)

                    {

                        useAsync = ma[0].UseAsyncProcessing;

                        if (ma[0].RequireSessionState != HttpSessionStateRequirement.UseDefault)

                            sessionReq = ma[0].RequireSessionState;

                    }

                    //6种Handler,根据是否异步,session状态返回指定的Handler

                    switch (sessionReq)

                    {

                        case HttpSessionStateRequirement.Read:

                            if (!useAsync)

                                return new AjaxSyncHttpHandlerSessionReadOnly(p[i]);

                            else

                                return new AjaxAsyncHttpHandlerSessionReadOnly(p[i]);

                        case HttpSessionStateRequirement.ReadWrite:

                            if (!useAsync)

                                return new AjaxSyncHttpHandlerSession(p[i]);

                            else

                                return new AjaxAsyncHttpHandlerSession(p[i]);

                        case HttpSessionStateRequirement.None:

                            if (!useAsync)

                                return new AjaxSyncHttpHandler(p[i]);

                            else

                                return new AjaxAsyncHttpHandler(p[i]);

                        default:

                            if (!useAsync)

                                return new AjaxSyncHttpHandlerSession(p[i]);

                            else

                                return new AjaxAsyncHttpHandlerSession(p[i]);

                    }

                }

            }

            break;

    }

    return null;

}

  3. 反射执行方法

  当获得一个处理本次请求的Handler后,就可以在其ProcessRequest(异步为BeginProcessRequest)执行指定的方法。要执行一个页面对象的方法,我们必须知道指定页面所在的程序集,名称空间,页面类的名称以及方法的名称。这似乎符合我们前面:名称空间.类名称.方法名称的调用方式。为了与一般请求区分开,让组件具有足够的独立性,ajaxpro只拦截符合"ajaxpro/*.ashx格式的请求,这说明我们的ajax请求也要符合这个格式。如:http://localhost:50712/ajaxpro/AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode.ashx,这个格式由前台脚本自动生成,并不需要我们去构造。仔细观察,会发现AjaxProNamespace.AjaxProPage,TestAjaxProSourceCode 就是页面类的完全限定名:名称空间.类名称,程序集名称,通过这个我们就可以生成具体的Type,然后进行反射获取信息。那么方法的名称呢?ajaxpro将其放在http header 中,名称为:X-AjaxPro-Method。有了这些信息,就可以反射执行方法了。这里核心代码为:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

internal void Run()

{

    try

    {

        //设置输出结果不缓存(这不一定是我们想要的)

        p.Context.Response.Expires = 0;

        p.Context.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);

        p.Context.Response.ContentType = p.ContentType;

        p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8;

        //验证ajax请求

        if (!p.IsValidAjaxToken())

        {

            p.SerializeObject(new System.Security.SecurityException("The AjaxPro-Token is not valid."));

            return;

        }

        //方法参数对象数组

        object[] po = null;

        //请求处理结果

        object res = null;

        try

        {

            //获取参数

            po = p.RetreiveParameters();

        }

        catch (Exception ex){}

        //获取缓存的Key

        string cacheKey = p.Type.FullName + "|" + p.GetType().Name + "|" + p.AjaxMethod.Name + "|" + p.GetHashCode();

        if (p.Context.Cache[cacheKey] != null)

        {

            //如果缓存存在,则直接使用缓存

            p.Context.Response.AddHeader("X-" + Constant.AjaxID + "-Cache""server");

            p.Context.Response.Write(p.Context.Cache[cacheKey]);

            return;

        }

        try

        {

            if (p.AjaxMethod.IsStatic)

            {

                //使用反射调用静态方法

                try

                {

                    res = p.Type.InvokeMember(

                        p.AjaxMethod.Name,

                        System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod,

                        nullnull, po);

                }

                catch (Exception ex){}

            }

            else

            {

                try

                {

                    //创建实例对象,反射调用实例方法

                    object c = (object)Activator.CreateInstance(p.Type, new object[] { });

                    if (c != null)

                    {

                        res = p.AjaxMethod.Invoke(c, po);

                    }

                }

                catch (Exception ex){}

            }

        }

        catch (Exception ex){}

        try

        {

            //判断结果是不是xml,如是设置ContentType

            if (res != null && res.GetType() == typeof(System.Xml.XmlDocument))

            {

                p.Context.Response.ContentType = "text/xml";

                p.Context.Response.ContentEncoding = System.Text.Encoding.UTF8;

                ((System.Xml.XmlDocument)res).Save(p.Context.Response.OutputStream);

                return;

            }

            string result = null; ;

            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            try

            {

                result = p.SerializeObject(res);

            }

            catch (Exception ex){}

            //如果需要缓存,则将结果写入缓存

            if (p.ServerCacheAttributes.Length > 0)

            {

                if (p.ServerCacheAttributes[0].IsCacheEnabled)

                {

                    p.Context.Cache.Add(cacheKey, result, null, DateTime.Now.Add(p.ServerCacheAttributes[0].CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);

                }

            }

        }

        catch (Exception ex){}

    }

    catch (Exception ex){}

}

三、总结

  我们总结一下ajaxpro的核心处理流程,它通过一个IHttpHandlerFactory拦截指定格式的url,然后从中获取类型的完全限定名生成类型对象,接着通过反射获取标记方法的特性,生成一个自定义的实现IHttpHandler接口的对象;在其ProcessRequest方法中,从http headers获取方法名称,通过反射进行参数映射并执行函数。

  ajaxpro 具有如下优点:

  1. 配置简单。

  2. 可以配合其它组件一起使用。

  3. 封装前台脚本,我们不用自己封装或者使用其它脚本库。

  4. 对返回值处理,我们可以返回简单类型或者复杂类型都会自动序列化。  

  缺点是:

  1. 页面会多出4个请求。尽管会利用304缓存,但还是需要一次请求-响应的过程。

  2. ajax无法使用Get请求。由于自定义了url格式,使用这种格式就无法用Get请求了,我们知道Get请求是可以被浏览器缓存的,雅虎前端优化建议中有一条就是多用get请求。事实上,应该把名称空间.类名称,程序集放到http header中,然后提供了一个type类型的参数让我们自由选择。

  3. 与<form runat="server">绑定。目的是用了为我们生成前台脚本,但如果我们希望用.html文件 + .aspx.cs 的方式就不能用了(博客园有些页面就用了这种方式);甚至我们的接口可能要给移动端使用,这种方便就变成了限制。

  4. 反射。这样效率是比较低的,它甚至没有像我们之前的页面类一样,对MethodInfo进行缓存。

  可以看出,如果在不太计较效率的情况,这个组件还是值得使用的。这里只是做一个核心的介绍,里面还有很多其它功能,这是ajaxpro组件的源代码,有兴趣的朋友可以研究研究。

时间: 2024-08-04 14:18:07

编写轻量ajax组件02-AjaxPro浅析的相关文章

编写轻量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提供了足够的扩展性,我

Vue.js:轻量高效的前端组件化方案(转载)

摘要:Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.在前端纷繁复杂的生态中,Vue.js有幸受到一定程度的关注,目前在GitHub上已经有5000+的star.本文将从各方面对Vue.js做一个深入的介绍. Vue.js 是我在2014年2月开源的一个前端开发库,通过简洁的 API 提供高效的数据绑定和灵活的组件系统.在前端纷繁复杂的生态中,Vue.js有幸受到一定程度的关注,目前在 GitHub上已经有5000+的star.本文将从各方面对Vue.js做一个深入的介绍. 开发

oracle从入门到精通复习笔记续集之PL/SQL(轻量版)

复习内容: PL/SQL的基本语法.记录类型.流程控制.游标的使用. 异常处理机制.存储函数/存储过程.触发器. 为方便大家跟着我的笔记练习,为此提供数据库表文件给大家下载:点我下载 为了要有输出的结果,在写PL/SQL程序前都在先运行这一句:set serveroutput on结构:declare--声明变量.类型.游标begin--程序的执行部分(类似于java里的main()方法)exception--针对begin块中出现的异常,提供处理的机制--when...then...--whe

推荐轻量友好的.NET测试断言工具Shoudly

Shoudly是一个轻量的断言(Assertion)框架,用于补充.NET框架下的测试工具.Shoudly将焦点放在当断言失败时如何简单精准的给出很好的错误信息. Shouldly在GitHub的开源地址:https://github.com/shouldly/shouldly Shouldly的官方文档:http://docs.shouldly-lib.net/ 为什么要Shoudly? 我们知道通常测试代码中一个断言是这样写的: Assert.That(contestant.Points,

基于netty轻量的高性能分布式RPC服务框架forest&lt;下篇&gt;

基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开发的RPC框架,除了常规的点对点调用外,Motan还提供服务治理功能,包括服务节点的自动发现.摘除.高可用和负载均衡等. 架构概述 Forest中分为服务提供方(RPC Server),服务调用方(RPC Client)和服务注册中心(Registry)三个角色. Server提供服务,向Registry注册

SqlSugar轻量ORM

蓝灯软件数据股份有限公司项目,代码开源. SqlSugar是一款轻量级的MSSQL ORM ,除了具有媲美ADO的性能外还具有和EF相似简单易用的语法. 学习列表 0.功能更新 1.SqlSugar基础应用 2.使用SqlSugar处理大数据 3.使用SqlSugar实现Join  待更新 4.使用SqlSugar实现分页+分组+多列排序 待更新 5.节点故障如何进行主从调换 一.介简 优点: 1.优越的性能,查询使用  reflection.emit 创建IL语言+委托绑定 然后对该对象进行

使用XCB编写X Window程序(02):在窗口中绘图

在上一篇中,我展示了怎么连接X服务器以及怎么创建一个窗口.创建窗口是编写GUI程序的根本.在GUI编程中还有另外两个重点,其一是事件处理,其二是在窗口中绘图.这一篇中,将展示如何使用XCB在窗口中进行绘图. 先看一个示例代码及其运行效果,代码如下: 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 #include <xcb/xcb.h> 5 6 int 7 main () 8 { 9 /* geometric objects

Trumbowyg - 轻量的 WYSIWYG 编辑器

Trumbowyg 是一个轻量,可定制的 jQuery 所见即所得(WYSIWYG)的编辑器插件.美丽的设计,生成语义化代码,带有功能强大的 API .编辑器和生成的代码进行了优化以支持 HTML5.兼容大部分浏览器,如 IE8+.Chrome.Opera 和 Firefox . 您可能感兴趣的相关文章 Web 开发中很实用的10个效果[附源码下载] 精心挑选的优秀jQuery Ajax分页插件和教程 12款经典的白富美型 jQuery 图片轮播插件 让网站动起来!12款优秀的 jQuery 动

一种简单,轻量,灵活的C#对象转Json对象的方案

简单,是因为只有一个类 轻量,是因为整个类代码只有300行 灵活,是因为扩展方式只需要继承重写某个方法即可 首先我将这个类称之为JsonBuilder,我希望它以StringBuilder的方式来实现Json字符串的转换 public class JsonBuilder { protected StringBuilder Buff = new StringBuilder(4096);//字符缓冲区 public string ToJsonString(object obj) { .......