zoukankan      html  css  js  c++  java
  • (转)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; }
    }

        最后,需要说明的是本文转载于:http://www.cnblogs.com/cwy173/,本文中提及的只是Ajax机制中的一小部分,例如数据加密、同步异步机制、访问服务器端的属性及Session、返回表状数据及数据转换等等都未涉及,权当抛砖引玉吧。有很多理解不到位或言辞不严谨的地方,切勿全信,仅供参考!

    作者:wpf之家
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    hdu 1269 迷宫城堡 (并查集)
    hdu 1272 小希的迷宫 (深搜)
    hdu 1026 Ignatius and the Princess I (深搜)
    hdu 1099 Lottery
    hdu 1068 Girls and Boys (二分匹配)
    几个基础数位DP(hdu 2089,hdu 3555,uestc 1307 windy 数)
    hdu 1072 Nightmare (广搜)
    hdu 1398 Square Coins (母函数)
    hdu 1253 胜利大逃亡 (深搜)
    hdu 1115 Lifting the Stone (求重心)
  • 原文地址:https://www.cnblogs.com/wpf123/p/2347407.html
Copyright © 2011-2022 走看看