• 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; }
    }

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

  • 相关阅读:
    php极光网络一键登录(yii框架)
    Sublime Text3将多行转为为一行 | Sublime Text 快速分别独立选中多行
    mysql 将时间戳转换成日期格式
    Vant主题定制修改颜色样式
    TypeError: this.getOptions is not a function 引入less一直报错
    export defaul 和 export定义和区别
    Vue vant引入,tabbar封装使用示例
    php去除富文本编辑器中的内容格式
    ES6:高级数组函数,filter/map/reduce
    [BZOJ2793][Poi2012]Vouchers
  • 原文地址:https://www.cnblogs.com/cwy173/p/1612809.html
Copyright © 2020-2023  润新知