AjaxPro实现机制探讨——Ajax是如何调用服务器端C#方法的?

AjaxPro实现机制探讨——Ajax是如何调用服务器端C#方法的?

       谈起 Ajax 做过 web 开发的都非常熟悉,就是通过 xmlhttp request 与服务器端通信而避免页面刷新。关于 Ajax 是如何运作的,网上有很多帖子解释其各 js 文件的作用及调用 xmlhttp 的原理。但 Ajax 到底是怎么调用服务器端的 C# 代码的呢?怎么让后台的方法运行并将结果反馈给 xmlhttp 的呢?曾经有个同事问起我这个问题,我还真懵了!本以为象 .Net 1.1 下通过 form 传递必要的 EventName EventPara 等参数传给服务器端继而解析后执行对应的事件一样来调用 C# 代码的( .net 调用事件机制也不全是这么回事,待探讨),但通过仔细研究,发现原来远不是这么回事,而网上更深入的文章却少之又少。

我们由浅到深吧,先看看相对表象的东西,即前台 Ajax 相关的 JavaScript 代码部分。之所以说相对肤浅和表象,是因为这些资料很多网友已经撰文解读过。

凡要使用 AjaxPro ,我们大致要做以下工作:

1)   在项目中引用 AjaxPro.dll (我用的是 AjaxPro.2.dll ,版本 6.6.13.2 ),并在 web.config httpHandlers 配置节添加:
< add verb = " POST,GET " path = " ajaxpro/*.ashx " type = " AjaxPro.AjaxHandlerFactory, AjaxPro.2 " />

2)   在要使用 Ajax 功能的页面 .cs 文件上注册 Ajax ,例如:

        protected void Page_Load( object sender, EventArgs e)

        {

            // 注册Ajax

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

        }

3)   .cs 文件中声明可以被 Ajax 调用的函数(或属性),如:

        [AjaxPro. AjaxMethod ]

        public string GetChild( string parentId)

        {           

            return "return value from .cs file" ;

        }

4)   .aspx 文件中用 JavaScript 调用 Ajax ,如:

            < script language ="javascript">           

                    var items =  DynLoadTree.Default.GetChild( "aa" ).value;  // 通过Ajax 调用后台代码

                    alert(items);

            </ script >

做好以上四步,我们就基本实现了 Ajax 页面不刷新的功能了。那么它是怎样通过 xmlhttp 与服务器通讯的呢?运行后我们可以看到 HTML 文件的源代码多了几行 .ashx 文件的引用:

<script type="text/javascript" src="/ajaxpro/prototype.ashx"></script>
<script type="text/javascript" src="/ajaxpro/core.ashx"></script>
<script type="text/javascript" src="/ajaxpro/converter.ashx"></script>
<script type="text/javascript" src="/ajaxpro/DynLoadTree.Default,DynLoadTree.ashx"></script>

   实际上这些 .ashx 就是在上面第 2 AjaxPro. Utility .RegisterTypeForAjax 注册Ajax 时自动将这些引用添加到Html 文档输出的。那这些文件是什么文件呢?再看第1 步中在web.config 中添加到httpHandlers 节中的配置,它告诉系统凡是收到ajaxpro 路径下已经ashx 为后缀的请求就全部交给AjaxPro.AjaxHandlerFactory 这个类来处理,而这些ashx 经过处理后返回的就是一些JavaScript 文件,和普通的js 引用没有实质区别。

  我们首先看看“ DynLoadTree.Default,DynLoadTree.ashx ”的内容:
if(typeof DynLoadTree == "undefined") DynLoadTree={};
DynLoadTree.Default_class = function() {};
Object.extend(DynLoadTree.Default_class.prototype, Object.extend(new AjaxPro.AjaxClass(), {   GetChild: function(parentId) {
return this.invoke("GetChild", {"parentId":parentId}, this.GetChild.getArguments().slice(1));
},url: '/ajaxpro/DynLoadTree.Default,DynLoadTree.ashx'}));
DynLoadTree.Default = new DynLoadTree.Default_class();

原来我们 DynLoadTree.Default 是在这里定义的,而这个 GetChild 方法最终是调用“ this.invoke("GetChild", {"parentId":parentId}, this.GetChild.getArguments().slice(1)); ”的,而 invoke 方法是在“ core.ashx ”中定义的。在 core.ashx 中定义了很多 Ajax 核心的 js 方法,例如 Object.extand 实现简单的继承(或阅扩展)。在 invoke 方法里,首先是 new 了一个 XmlHttp 对象,然后重点做了几件事:

this.xmlHttp.open("POST", this.url, async);
   this.xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  
this.xmlHttp.setRequestHeader("X-" + AjaxPro.ID + "-Method", method);
  
this.xmlHttp.send(json);

xmlHttp.open 说明了是向哪个服务器 url 发送请求,是同步请求还是异步请求。接下来就设置 Content-Type http header ,然后再将 method 设置到 http header 中,以让服务器端知道要调用什么方法,最后 send 出去,同时参数 json 包含了调用这个方法所需的参数。至此,利用 xmlhttp 已经将请求发送给服务器了,接下来就等待服务器的反馈结果了(对于同步和异步不同的调用方式,对结果的处理是有区别的)。

但是,为什么这样一个请求给服务器后,服务器就自动调用制定的 method 呢?如果仔细一点,你可以发现 xmlHttp.open 里的 this.url 到底是什么?是要调用的页面的地址么?实际不是,这个 this.url 的值是“ /ajaxpro/DynLoadTree.Default,DynLoadTree.ashx ”。第一次看到这里的时候,我很诧异,怎么这个 xmlhttp 请求也发给一个 ashx 文件了呢?难道 ashx 文件不仅仅是用来动态生成 js 文件的么?同上,在 web.config 中已经配置了凡是 ashx 文件都交由类 AjaxPro.AjaxHandlerFactory 来处理,要想明白其中的奥秘,还得看看 AjaxHandlerFactory 里到底都干了些什么。为此,我用 Reflector AjaxPro.2.dll 文件进行反编译(我的资源里提供下载),看了 AjaxHandlerFactory 的代码才大彻大悟!

原来,在 AjaxHandlerFactory GetHandler 方法里是这么写的:

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

{

    ……

    string str2 = requestType;

    if (str2 != null )

    {

        if (!(str2 == "GET" ))

        {

            if (str2 == "POST" )

            {

                if (!(! Utility . Settings . OnlyAllowTypesInList || flag))

                {

                    return null ;

                }

                IAjaxProcessor [] processorArray = new IAjaxProcessor [] { new XmlHttpRequestProcessor (context, type), new IFrameProcessor (context, type) };

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

                {

                    if (processorArray[i]. CanHandleRequest )

                     {

                        if (exception != null )

                        {

                            processorArray[i]. SerializeObject ( new NotSupportedException ( "This method is either not marked with an AjaxMethod or is not available." ));

                            return null ;

                        }

                         AjaxMethodAttribute [] customAttributes = ( AjaxMethodAttribute []) processorArray[i]. AjaxMethod . GetCustomAttributes ( typeof ( AjaxMethodAttribute ), true );

                         bool useAsyncProcessing = false ;

                        HttpSessionStateRequirement readWrite = HttpSessionStateRequirement . ReadWrite ;

                        if ( Utility . Settings . OldStyle . Contains ( "sessionStateDefaultNone" ))

                        {

                            readWrite = HttpSessionStateRequirement . None ;

                        }

                        if (customAttributes. Length > 0 )

                        {

                            useAsyncProcessing = customAttributes[ 0 ]. UseAsyncProcessing ;

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

                             {

                                readWrite = customAttributes[ 0 ]. RequireSessionState ;

                            }

                        }

                        switch (readWrite)

                        {

                            case HttpSessionStateRequirement . ReadWrite :

                                 if (useAsyncProcessing)

                                {

                                    return new AjaxAsyncHttpHandlerSession (processorArray[i]);

                                }

                                return new AjaxSyncHttpHandlerSession (processorArray[i]);

 

                            case HttpSessionStateRequirement . Read :

                                if (useAsyncProcessing)

                                {

                                    return new AjaxAsyncHttpHandlerSessionReadOnly (processorArray[i]);

                                }

                                return new AjaxSyncHttpHandlerSessionReadOnly (processorArray[i]);

 

                            case HttpSessionStateRequirement . None :

                                 if (useAsyncProcessing)

                                {

                                    return new AjaxAsyncHttpHandler (processorArray[i]);

                                }

                                return new AjaxSyncHttpHandler (processorArray[i]);

                         }

                        if (!useAsyncProcessing)

                        {

                            return new AjaxSyncHttpHandlerSession (processorArray[i]);

                        }

                        return new AjaxAsyncHttpHandlerSession (processorArray[i]);

                    }

                }

            }

        }

        else

        {

            switch (fileNameWithoutExtension. ToLower ())

            {

                 case "prototype" :

                    return new EmbeddedJavaScriptHandler ( "prototype" );

                case "core" :

                    return new EmbeddedJavaScriptHandler ( "core" );

                ……

                default :                   

                    return new TypeJavaScriptHandler (type);

            }

        }

    }

    return null ;

}

 

它首先对requestType 进行判断,如果是“GET ”请求,则说明是html 里对被引用的ashx 文件的下载请求,则调用相应的Handler 去生成对应的JavaScript 内容输出到客户端;如果是“POST” 请求,则说明是通过XMLHTTP 发送过来的,是请求调用服务器端方法的,则返回相应的Handler 利用反射机制调用请求的方法。

首先看看“GET ”请求,对“GET ”请求的处理很简单,根据不同的文件名返回不同的Handler, 对于“core ”及“prototype ”则返回 EmbeddedJavaScriptHandler ,对于“DynLoadTree.Default,DynLoadTree.ashx ”则返回 TypeJavaScriptHandler 。在 EmbeddedJavaScriptHandler 中,构造函数的参数表示要请求的是哪个文件,然后在ProcessRequest 函数中提取指定的文件内容并输出到客户端,其实这些文件内容都是固定的,且已经放在资源里的:

internal class EmbeddedJavaScriptHandler : IHttpHandler
{
    // Fields
    private string fileName ;       // Methods
    internal EmbeddedJavaScriptHandler ( string fileName)
    {
        this . fileName = fileName;
    }       public void ProcessRequest ( HttpContext context)
    {
       ……
        string [] strArray = this . fileName . Split ( new char [] { ',' });
        Assembly executingAssembly = Assembly . GetExecutingAssembly ();        
       
for (
int i = 0 ; i < strArray. Length ; i++)
        {
            Stream manifestResourceStream = executingAssembly. GetManifestResourceStream ( "AjaxPro.2." + strArray[i] + ".js" );
            if (manifestResourceStream != null )
            {
                StreamReader reader = new StreamReader (manifestResourceStream);
                context. Response . Write (reader. ReadToEnd ());
                context. Response . Write ( "/r/n" );
                reader. Close ();                 if ((strArray[i] == "prototype" ) && Utility . Settings . OldStyle . Contains ( "objectExtendPrototype" ))
                {
                    context. Response . Write ( "/r/nObject.prototype.extend = function(o, override) {/r/n/treturn Object.extend.apply(this, [this, o, override != false]);/r/n}/r/n" );
                }
            }
        }
        ………
    }
   ……
}  

  
对于“ DynLoadTree.Default,DynLoadTree.ashx ”的请求,则交给
TypeJavaScriptHandler 处理:

internal class TypeJavaScriptHandler : IHttpHandler , IReadOnlySessionState , IRequiresSessionState

{

    // Fields

    private Type type ;

 

    // Methods

    internal TypeJavaScriptHandler ( Type type);

    public void ProcessRequest ( HttpContext context);

 

    // Properties

    public bool IsReusable { get ; }
}

   ProcessRequest 会根据Type 动态生成JavaScript 内容并输出到客户端。     对于requestType 是“POST ”的请求,则返回相应的Handler 进行处理。以 AjaxSyncHttpHandler 为例:

internal class AjaxSyncHttpHandler : IHttpHandler
{
    // Fields
    private IAjaxProcessor p ;       // Methods
    internal AjaxSyncHttpHandler ( IAjaxProcessor p)
    {
        this . p = p;
    }       public void ProcessRequest ( HttpContext context)
    {
        new AjaxProcHelper ( this . p ). Run ();
    }       // Properties
    public bool IsReusable
    {
        get
        {
            return false ;
        }
    }
}
   
其中ProcessRequest 方法就就新建一个AjaxProcHelper 对象,用该对象的Run 方法来处理实质请求。可以简略看看AjaxProcHelper.Run 的代码:

internal void Run ()
{
    ……
    this . p . Context . Response . Expires = 0 ;
    this . p . Context . Response . Cache . SetCacheability ( HttpCacheability . NoCache );
    this . p . Context . Response . ContentType = this . p . ContentType ;
    this . p . Context . Response . ContentEncoding = Encoding . UTF8 ;
    ……
    object [] args = null ;
    object o = null ;
    args = this . p . RetreiveParameters ();     
   
string key = string . Concat ( new object [] { this . p . Type . FullName , "|" , this . p . GetType (). Name , "|" , this . p . AjaxMethod . Name , "|" , this . p . GetHashCode () });
    if ( this . p . Context . Cache [key] != null )
    {
        this . p . Context . Response . AddHeader ( "X-AjaxPro-Cache" , "server" );
         this . p . Context . Response . Write ( this . p . Context . Cache [key]);
     }
     else
     {
         ……
           if ( this . p . AjaxMethod . IsStatic )
          {
            o = this . p . Type . InvokeMember ( this . p . AjaxMethod . Name , BindingFlags . InvokeMethod | BindingFlags . Public | BindingFlags . Static | BindingFlags . IgnoreCase , null , null , args);
          }
         else
         {
               ……
               object obj3 = Activator . CreateInstance ( this . p . Type , new object [ 0 ]);
               o = this . p . AjaxMethod . Invoke (obj3, args);
         }
         ……                  
          if ((o != null ) && (o. GetType () == typeof ( XmlDocument )))
         {
             this . p . Context . Response . ContentType = "text/xml" ;
             (( XmlDocument ) o). Save ( this . p . Context . Response . OutputStream );
          }
        ……              
     }
}  

    可以清晰的看到,Run 中是通过反射机制调用相应的方法,再将结果写入context 输出到客户端的。
  另外,我们也可以清晰的看到 Utility 中对RegisterTypeForAjax 的几个重载及实现方式:         
                     public
static void RegisterTypeForAjax ( Type type);
         public static void RegisterTypeForAjax ( Type type, Page page);

 
同时,也可以看看AjaxMethodAttribute 的定义(有关Attribute MSDN 中有详细的描述和实例):

[ AttributeUsage ( AttributeTargets . Method , AllowMultiple = false )]
public class AjaxMethodAttribute : Attribute
{
    // Fields
    private HttpSessionStateRequirement requireSessionState ;
    private bool useAsyncProcessing ;       // Methods
    public AjaxMethodAttribute ();
    public AjaxMethodAttribute ( HttpSessionStateRequirement requireSessionState);

    [ Obsolete ( "The use of this argument is currently in beta state, please report any problems to bug@schwarz-interactive.de." )]
    public AjaxMethodAttribute ( bool useAsyncProcessing);    

    [
Obsolete ( "The recommended alternative is AjaxPro.AjaxServerCacheAttribute." , true )]
    public AjaxMethodAttribute ( int cacheSeconds);

    [ Obsolete ( "The recommended alternative is AjaxPro.AjaxNamespaceAttribute." , true )]
    public AjaxMethodAttribute ( string methodName);

 

 

 

 

    [ Obsolete ( "The use of this argument is currently in beta state, please report any problems to bug@schwarz-interactive.de." )]
    public AjaxMethodAttribute ( HttpSessionStateRequirement requireSessionState, bool useAsyncProcessing);    

 

 

 

    [ Obsolete ( "The recommended alternative is AjaxPro.AjaxServerCacheAttribute." , true )]
    public AjaxMethodAttribute ( int cacheSeconds, HttpSessionStateRequirement requireSessionState);    

 

    [ Obsolete ( "The recommended alternative for methodName is AjaxPro.AjaxNamespaceAttribute." , true )]
    public AjaxMethodAttribute ( string methodName, HttpSessionStateRequirement requireSessionState);    

 

    [ Obsolete ( "The recommended alternative for methodName is AjaxPro.AjaxNamespaceAttribute." , true )]
    public AjaxMethodAttribute ( string methodName, int cacheSeconds);

 

    [ Obsolete ( "The recommended alternative for methodName is AjaxPro.AjaxNamespaceAttribute." , true )]
    public AjaxMethodAttribute ( string methodName, int cacheSeconds, HttpSessionStateRequirement requireSessionState);       // Properties
   

    internal HttpSessionStateRequirement RequireSessionState { get ; }
    internal bool UseAsyncProcessing { get ; }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值