• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    编写轻量ajax组件第三篇实现

    通过之前的介绍,我们知道要执行页面对象的方法,核心就是反射,是从请求获取参数并执行指定方法的过程。实际上这和asp.net mvc框架的核心思想很类似,它会解析url,从中获取controller和action名称,然后激活controller对象,从请求获取action参数并执action。在web form平台上,我们把方法写在.aspx.cs中,要实现的就是在页面对象还未生成的情况下,执行指定的方法,然后返回结果。

      我们先看实现后几个调用例子,这些功能也可以组合使用:      

     [AjaxMethod]
        public void Test1(int index)
        {
          //简单调用
        }
    
        [AjaxMethod]
        public string Test2(Test test)
        {
          return "参数为一个Test实例";
        }
    
        [AjaxMethod(OutputCache = 20)]
        public string Test3(int index)
        {
          return "输出结果缓存20秒";
        }
    
        [AjaxMethod(ServerCache = 20)]
        public string Test4()
        {
          return "在服务端缓存20秒";
        }
    
        [AjaxMethod(SessionState=SessionState.None)]
        public void Test5()
        {
          //Session未被加载
        }
    
        [AjaxMethod(SessionState = SessionState.ReadOnly)]
        public void Test6()
        {
          //Session只能读不能写
        }
    
        [AjaxMethod(SessionState = SessionState.ReadWrite)]
        public void Test7()
        {
          //Session可以读写
        }
    
        [AjaxMethod(IsAsync = true)]
        public void Test8()
        {
          //异步调用
        }  
    
    

      前面我们已经熟悉基本的执行流程,现在直接进入主题。

    Ajax约定

      通常现在主流浏览器在使用ajax发送异步请求时,请求头都会带上一个:X-Requested-With:XMLHttpRequest 的标记。我们也可以直接通过这个标记来判断是不是ajax请求,不过项目中可能有用其它的组件,为了不相互影响,我们加入一个自定义的请求头。这里为:

    internal static class AjaxConfig
     {
      /// summary>
      /// 请求头Ajax标记键
      /// /summary>
      public const string Key = "AjaxFlag";
    
      /// summary>
      /// 请求头Ajax标记值
      /// /summary>
      public const string Value = "XHR";
    
      /// summary>
      /// 请求头Ajax方法标记
      /// /summary>
      public const string MethodName = "";
     }
    
    

      意思是如果http 的请求头包含一个 AjaxFlag : XHR,就是我们要处理的。另外http header的MethodName就表示我们要执行的方法的名称。

    AjaxMethodAttribute标记属性

      标记属性是给反射用的,在这里定义我们需要的一些功能。我们希望有:

      1. 可以配置Session状态

      2. 支持异步Handler

      3. 支持Get缓存

      4. 支持服务端缓存

      定义如下,用AttributeUsag标记该标记只能用于方法上。

      /// summary>
      /// ajax方法标记属性
      /// /summary>
      [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
      public class AjaxMethodAttribute : Attribute
      {
        public AjaxMethodAttribute()
        {      
        }
    
        private SessionState _sessionState = SessionState.None;
        private int _outputCache = 0;
        private int _serverCache = 0;
        private ContentType _contentType = ContentType.Plain;
        private bool _isUseAsync = false;
    
        /// summary>
        /// session状态
        /// /summary>
        public SessionState SessionState 
        {
          get { return _sessionState; }
          set { _sessionState = value; }
        }
    
        /// summary>
        /// 客户端缓存时间,以秒为单位。该标记只对get请求有效
        /// /summary>
        public int OutputCache 
        {
          get { return _outputCache; }
          set { _outputCache = value; }
        }
    
        /// summary>
        /// 服务端缓存时间,以秒为单位
        /// /summary>
        public int ServerCache 
        {
          get { return _serverCache; }
          set { _serverCache = value; }
        }    
    
        /// summary>
        /// 输出类型(默认为text/plain)
        /// /summary>
        public ContentType ContentType 
        {
          get { return _contentType; }
          set { _contentType = value; }
        }
    
        /// summary>
        /// 使用启用异步处理
        /// /summary>
        public bool IsAsync 
        {
          get { return _isUseAsync; }
          set { _isUseAsync = value; }
        }
      }
    
      /// summary>
      /// Session状态
      /// /summary>
      public enum SessionState
      {
        None,
        ReadOnly,
        ReadWrite    
      }
    
      /// summary>
      /// 输出内容类型
      /// /summary>
      public enum ContentType
      {
        Plain,
        Html,
        XML,
        Javascript,
        JSON
      }
    
    

    各种处理程序和AjaxHandlerFactory

      按照上一篇的说法,具体的Handler主要分为两类,异步和非异步;这两类下,对于Session的状态又有3三种,不支持、只支持读(实现IReadOnlySessionState接口)、支持读写(实现IRequiresSessionState接口)。IReadOnlySessionState和IRequiresSessionState都只是标记接口(无任何方法,其实应该用标记属性实现比较合理)。异步的Handler需要实现IHttpAsyncHandler接口,该接口又实现了IHttpHandler。Handler的ProcessRequest方法(或BeginProcessRequest)就是我们要执行方法的地方。定义如下:

      非异步状态的Handler:

       

     //不支持Session
      internal class SyncAjaxHandler : IHttpHandler
      {
        private Page _page;
        private CacheMethodInfo _cacheMethodInfo;
    
        internal SyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
        {
          _page = page;
          _cacheMethodInfo = cacheMethodInfo;
        }
    
        public void ProcessRequest(HttpContext context)
        {
          //执行方法(下面详细介绍)
          Executor.Execute(_page, context, _cacheMethodInfo);
        }
    
        public bool IsReusable
        {
          get { return false; }
        }
    
        public static SyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
        {
          switch (state)
          {
            case SessionState.ReadOnly:
              return new SyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
            case SessionState.ReadWrite:
              return new SyncAjaxSessionHandler(page, cacheMethodInfo);
            default:
              return new SyncAjaxHandler(page, cacheMethodInfo);
          }
        }
      }
    
      //支持只读Session
      internal class SyncAjaxSessionReadOnlyHandler : SyncAjaxHandler, IReadOnlySessionState
      {
        internal SyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
          : base(page, cacheMethodInfo)
        {
        }
      }
    
      //支持读写Session
      internal class SyncAjaxSessionHandler : SyncAjaxHandler, IRequiresSessionState
      {
        internal SyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
          : base(page, cacheMethodInfo)
        {
        }
      } 
    
    

      异步状态的Handler:

      

     //不支持Session
      internal class ASyncAjaxHandler : IHttpAsyncHandler, IHttpHandler
      {
        private Page _page;
        private CacheMethodInfo _cacheMethodInfo;
    
        internal ASyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
        {
          _page = page;
          _cacheMethodInfo = cacheMethodInfo;
        }
    
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
          //执行方法(下面详细介绍)
          ActionPage, HttpContext, CacheMethodInfo> action = new ActionPage, HttpContext, CacheMethodInfo>(Executor.Execute);
          IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action);
          return result;
        }
    
        public void EndProcessRequest(IAsyncResult result)
        {
          ActionPage, HttpContext, CacheMethodInfo> action = result.AsyncState as ActionPage, HttpContext, CacheMethodInfo>;
          action.EndInvoke(result);
        }
    
        public void ProcessRequest(HttpContext context)
        {
          throw new NotImplementedException();
        }
    
        public bool IsReusable
        {
          get { return false; }
        }
    
        public static ASyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
        {
          switch (state)
          {
            case SessionState.ReadOnly:
              return new ASyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
            case SessionState.ReadWrite:
              return new ASyncAjaxSessionHandler(page, cacheMethodInfo);
            default:
              return new ASyncAjaxHandler(page, cacheMethodInfo);
          }
        }
      }
    
      //支持只读Session
      internal class ASyncAjaxSessionReadOnlyHandler : ASyncAjaxHandler, IReadOnlySessionState
      {
        internal ASyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
          : base(page, cacheMethodInfo)
        {
        }
      }
    
      //支持读写Session
      internal class ASyncAjaxSessionHandler : ASyncAjaxHandler, IRequiresSessionState
      {
        internal ASyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
          : base(page, cacheMethodInfo)
        {
        }
      }  
    
    

      AjaxHandlerFactory实现了IHandlerFactory接口,用来根据请求生成具体的Handler,它需要在web.config进行注册使用。AjaxHandlerFactory的GetHandler是我们拦截请求的第一步。通过请求头的AjaxFlag:XHR来判断是否需要我们处理,如果是,则创建一个Handler,否则按照普通的方式进行。由于我们的方法是写在.aspx.cs内的,我们的请求是.aspx后缀的,也就是页面(Page,实现了IHttpHandler)类型,Page是通过PageHandlerFactory创建的,PageHandlerFactory也实现了IHandlerFactory接口,表示它是用来创建处理程序的。所以我们需要用PageHandlerFactory来创建一个IHttpHandler,不过PageHandlerFactory的构造函数是protected internal类型的,我们无法直接new一个,所以需要通过一个CommonPageHandlerFactory继承它来实现。

      通过PageHandlerFactory获得Page后,结合方法名称,我们就可以反射获取AjaxMethodAttribute标记属性了。然后根据它的相关属性生成具体的Handler。具体代码如下:

      

     internal class CommonPageHandlerFactory : PageHandlerFactory { }
    
      internal class AjaxHandlerFactory : IHttpHandlerFactory
      {
        public void ReleaseHandler(IHttpHandler handler)
        {
        }
    
        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
          HttpRequest request = context.Request;
          if (string.Compare(request.Headers[AjaxConfig.Key], AjaxConfig.Value, true) == 0)
          {
            //检查函数标记
            string methodName = request.Headers[AjaxConfig.MethodName];
            if (methodName.IsNullOrEmpty())
            {
              Executor.EndCurrentRequest(context, "方法名称未正确指定!");
              return null;
            }
            try
            {          
              CommonPageHandlerFactory ajaxPageHandler = new CommonPageHandlerFactory();
              IHttpHandler handler = ajaxPageHandler.GetHandler(context, requestType, url, pathTranslated);
              Page page = handler as Page;
              if (page == null)
              {
                Executor.EndCurrentRequest(context, "处理程序类型必须是aspx页面!");
                return null;
              }
              return GetHandler(page, methodName, context);
            }
            catch
            {
              Executor.EndCurrentRequest(context, url + " 不存在!");
              return null;
            }
          }
          if (url.EndsWith(".aspx", StringComparison.CurrentCultureIgnoreCase))
          {
            CommonPageHandlerFactory orgPageHandler = new CommonPageHandlerFactory();
            return orgPageHandler.GetHandler(context, requestType, url, pathTranslated);
          }
          return null;
        }
    
        /// summary>
        /// 获取自定义处理程序
        /// /summary>
        /// param name="page">处理页面/param>
        /// param name="methodName">处理方法/param>
        /// param name="context">当前请求/param>
        private IHttpHandler GetHandler(Page page, string methodName, HttpContext context)
        {
          //根据Page和MethodName进行反射,获取标记属性(下面详细介绍)
          CacheMethodInfo methodInfo = Executor.GetDelegateInfo(page, methodName);
          if (methodInfo == null)
          {
            Executor.EndCurrentRequest(context, "找不到指定的Ajax方法!");
            return null;
          }
          AjaxMethodAttribute attribute = methodInfo.AjaxMethodAttribute;
          if (attribute.ServerCache > 0)
          {
            //先查找缓存
            object data = CacheHelper.TryGetCache(context);
            if (data != null)
            {
              Executor.EndCurrentRequest(context, data);
              return null;
            }
          }
          if (attribute.IsAsync)
          {
            //异步处理程序
            return ASyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
          }
          return SyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
        }
      }
    
    

      上面的CacheMethodInfo是用于缓存调用方法的相关信息的,第一篇我们有提到过优化缓存的一些方法,其中就包括缓存+委托。但这里我们并不直接缓存方法的MethodInfo,因为缓存MethodInfo的话,需要通过Invoke去执行,这样的效率比较低。这里我缓存的是方法的委托,该委托的签名为:Funcobject, object[], object>,该委托的返回值为object类型,表示可以返回任意的类型(我们可以在组件内部进行处理,例如如果是引用类型(非string),就将其序列化为json,但这里并没有实现)。该委托接收两个参数,第一个参数是方法所属的对象,如果是静态方法就是null;第二个参数是方法的参数,定义为object[]表示可以接收任意类型的参数。通过委托执行方法,与直接调用方法的效率差别就不是很大(对委托不熟悉的朋友可以参见:委托)。CacheMethodInfo的定义如下:

      

     /// summary>
      /// 缓存方法信息  
      /// /summary>
      sealed class CacheMethodInfo
      {
        /// summary>
        /// 方法名称
        /// /summary>
        public string MethodName { get; set; }
    
        /// summary>
        /// 方法委托
        /// /summary>
        public Funcobject, object[], object> Func { get; set; }
    
        /// summary>
        /// 方法参数
        /// /summary>
        public ParameterInfo[] Parameters { get; set; }
    
        /// summary>
        /// Ajax标记属性
        /// /summary>
        public AjaxMethodAttribute AjaxMethodAttribute { get; set; }
      }
    
    

    核心方法

    1. Eexcutor.GetDelegateInfo 获取方法相关信息

      该方法用于遍历页面类,获取所有AjaxMethodAttribute标记的方法信息,生成一个CacheMethodInfo对象,包括标记信息、方法名称、参数信息,以及最重要的方法委托。该对象会缓存在一个哈希表中,下次获取时,直接从内存获得。

           

     /// summary>
        /// 获取页面标记方法信息
        /// /summary>
        /// param name="page">页面对象/param>
        /// param name="methodName">方法名称/param>
        internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName)
        {
          if (page == null)
          {
            throw new ArgumentNullException("page");
          }
          Type type = page.GetType();
          //ajaxDelegateTable是一个Hashtable
          Dictionarystring, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionarystring, CacheMethodInfo>;
          if (dic == null)
          {
            dic = new Dictionarystring, CacheMethodInfo>();
            //遍历页面的所有MethodInfo
            IEnumerableCacheMethodInfo> infos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                               let ca = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false).FirstOrDefault()
                               where ca != null
                               select new CacheMethodInfo
                               {
                                 //方法标记属性
                                 AjaxMethodAttribute = ca as AjaxMethodAttribute,
                                 //方法名称
                                 MethodName = m.Name,
                                 //方法参数信息
                                 Parameters = m.GetParameters()
                               });
    
            if (infos.IsNullOrEmpty())
            {
              return null;
            }          
            for (int i = 0, length = infos.Count(); i  length; i++)
            {          
              CacheMethodInfo cacheMethodInfo = infos.ElementAt(i);          
              string name = cacheMethodInfo.MethodName;
              MethodInfo methodInfo = type.GetMethod(name);
              if (!dic.ContainsKey(name))
              {            
                //根据MethodInfo获取方法委托
                cacheMethodInfo.Func = ReflectionUtil.GetMethodDelegate(methodInfo);
                dic.Add(name, cacheMethodInfo);
              }
            }
            ajaxDelegateTable[type.AssemblyQualifiedName] = dic;
          }
          CacheMethodInfo currentMethodInfo = null;
          dic.TryGetValue(methodName, out currentMethodInfo);
          return currentMethodInfo;      
        }
    
    

      获取方法的委托的是通过一个ReflectionUtil获得的,该类主要用来优化反射,它通过Expression,可以将MethodInfo编译成Funcobject,object[],object>委托,为Type编译一个Funcobject>委托,用于创建实例对象。

      通过Expression优化反射

      Expression(表达式树)允许我们将代码逻辑以表达式的形式存储在树状结构里,然后在运行时去动态解析,实现动态编辑和执行代码。熟悉ORM框架的朋友对Expression肯定很熟悉,因为大部分方法都有一个ExpressionTDelegate>类型的参数。访问关系型数据库的本质还是sql语句,orm的工作就是为开发人员屏蔽这个过程,以面向对象的方式去读写数据库,而不是自己编写sql语句。例如,Users.Where(u => u.Age > 18) 就可查询年龄大于18的用户。这里不对应用在orm的过程进行详解,下面我们介绍如何用Expression并利用它来生成委托。

      .net定义了许多表达式类型,这些类型都派生自Expression,Expression是一个抽象类,而且是一个工厂类,所有类型的表达式都通过它来创建。如图:

      先看一个 1 * 2 + 2 例子,我们用表达树来描述来描述它:

               

    /*
           * a * b + 2 
           */
    
          /*
          直接操作
          int a = 1, b = 2;
          int result = a * 2 + 2;
          */
    
          /*
          通过委托调用
          Funcint, int, int> func = new Funcint, int, int>((a, b) => { return a * b + 2; });
          func(1, 2);
          */
    
          /*通过Expression调用*/
    
          //定义两个参数
          ParameterExpression pe1 = Expression.Parameter(typeof(int), "a");
          ParameterExpression pe2 = Expression.Parameter(typeof(int), "b");
          //定义一个常量
          ConstantExpression constExpression = Expression.Constant(2);      
    
          //参数数组
          ParameterExpression[] parametersExpression = new ParameterExpression[]{pe1,pe2};
    
          //一个乘法运算
          BinaryExpression multiplyExpression = Expression.Multiply(pe1, pe2);
    
          //一个加法运算
          BinaryExpression unaryExpression = Expression.Add(multiplyExpression, constExpression);
    
          //将上面的表达式转换为一个委托表达式
          LambdaExpression lambdaExpression = Expression.LambdaFuncint, int, int>>(unaryExpression, parametersExpression);
    
          //将委托编译成可执行代码
          Funcint,int,int> func = lambdaExpression.Compile() as Funcint,int,int>;
          Console.WriteLine(func(1, 2));
    
    

      可以看到我们最终将其编译为一个具体类型的委托了。下面看我们真正用到的方法是如何实现的,代码如下:

           

     public static Funcobject, object[], object> GetMethodDelegate(MethodInfo methodInfo)
        {
          if (methodInfo == null)
          {
            throw new ArgumentNullException("methodInfo");
          }
          //定义参数表达式,它表示委托的第一个参数
          ParameterExpression instanceExp = Expression.Parameter(typeof(object), "instance");
    
          //定义参数表达式,它表示委托的第二个参数
          ParameterExpression paramExp = Expression.Parameter(typeof(object[]), "parameters");
    
          //获取方法的参数信息数组
          ParameterInfo[] paramInfos = methodInfo.GetParameters();
    
          //参数表达式集合
          ListExpression> paramExpList = new ListExpression>();
    
          int length = paramInfos.Length;
          for (int i = 0; i  length; i++)
          {
            //获取paramExp参数数组的第i个元素
            BinaryExpression valueObj = Expression.ArrayIndex(paramExp, Expression.Constant(i));
    
            //将其转换为与参数类型一致的类型
            UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
    
            //添加到参数集合
            paramExpList.Add(valueCast);
          }  
    
          //方法所属的实例的表达式,如果为静态则为null
          UnaryExpression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceExp, methodInfo.ReflectedType);
    
          //表示调用方法的表达式 
          MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, paramExpList);
    
          //将表达式目录描述的lambda编译为可执行代码(委托)
          if (methodCall.Type == typeof(void))
          {
            ExpressionActionobject, object[]>> lambda = Expression.LambdaActionobject, object[]>>(methodCall, instanceExp, paramExp);
            Actionobject, object[]> action = lambda.Compile();
            return (instance, parameters) =>
            {
              action(instance, parameters);
              return null;
            };
          }
          else
          {
            UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
            ExpressionFuncobject, object[], object>> lambda = Expression.LambdaFuncobject, object[], object>>(castMethodCall, instanceExp, paramExp);
            return lambda.Compile();
          }
        }
    
    

      具体代码都有注释解释,最终我们获得了一个Funcobject,object[],object>类型的委托,它会作为CacheMethodInfo的属性进行缓存。有兴趣测试反射性能的朋友,也不妨去测试对比一下这几种方式执行的效率差别:1.直接执行方法 2.Emit 3. 缓存+委托 4.Delegate.DynamicInvoke。

    2. Executor.Execute 执行委托

      在执行委托前,我们需要先从请求获取参数,映射到方法。参数可以是简单的类型,如 string Test(int i,int j); 也可以是一个对象,如 string Test(User user); 如果是 string Test(User user1, User user2) 也行,提交参数时只需要加上 user1或 user2 前缀即可,例如 user1.Name,user2.Name。这里没有支持更多的匹配方式,像mvc,它还支持嵌套类型等等,这些可以自己去实现。如果参数是一个对象,我们可能需要为它的字段进行赋值,也可能为它的属性进行赋值。这里我们定义一个DataMember,用来表示字段或属性的父类。如:

      internal abstract class DataMember
      {
        public abstract string Name { get; }
        public abstract Type MemberType { get; }
        public abstract void SetValue(object instance,object value);
        public abstract object GetValue(object instance);
      }
    

      接着定义属性类型PropertyMember和字段类型FieldMember,分别继承了DataMember。

      PropertyMember定义:

      internal class PropertyMember : DataMember
      {
        private PropertyInfo property;
        public PropertyMember(PropertyInfo property)
        {
          if (property == null)
          {
            throw new ArgumentNullException("property");
          }
          this.property = property;
        }
    
        public override void SetValue(object instance, object value)
        {
          if (instance == null)
          {
            throw new ArgumentNullException("instance");
          }
          this.property.SetValue(instance, value, null);
        }
    
        public override object GetValue(object instance)
        {
          if (instance == null)
          {
            throw new ArgumentNullException("instance");
          }
          return this.property.GetValue(instance,null);
        }
    
        public override string Name
        {
          get { return this.property.Name; }
        }
    
        public override Type MemberType
        {
          get { return this.property.PropertyType; }
        }
      }
    
    

      FieldMember定义:

       

     internal class FieldMember : DataMember
      {
        private FieldInfo field;
        public FieldMember(FieldInfo field)
        {
          if (field == null)
          {
            throw new ArgumentNullException("field");
          }
          this.field = field;
        }
    
        public override void SetValue(object instance, object value)
        {
          if (instance == null)
          {
            throw new ArgumentNullException("instance");
          }
          this.field.SetValue(instance, value);
        }
    
        public override object GetValue(object instance)
        {
          if (instance == null)
          {
            throw new ArgumentNullException("instance");
          }
          return this.field.GetValue(instance);
        }
    
        public override string Name
        {
          get { return this.field.Name;}
        }
    
        public override Type MemberType
        {
          get { return this.field.FieldType; }
        }
      }
    
    

      定义一个DataMemberManager,用来遍历Type,获取所有字段和属性的,实现如下:

       

     internal static class DataMemberManager
      {
        /// summary>
        /// 获取实例字段/属性集合
        /// /summary>
        /// param name="type">类型/param>
        /// returns>/returns>
        public static ListDataMember> GetDataMember(Type type)
        {
          if (type == null)
          {
            throw new ArgumentNullException("type");
          }
          IEnumerablePropertyMember> propertyMembers = from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                             select new PropertyMember(property);
          IEnumerableFieldMember> fieldMembers = from field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                           select new FieldMember(field);
          ListDataMember> members = new ListDataMember>();
          foreach(var property in propertyMembers)
          {
            members.Add(property);
          }
          foreach (var field in fieldMembers)
          {
            members.Add(field);
          }
          return members;
        }
      }
    

      在前面我们定义的Handler的ProcessRequest方法中,我们调用了Executor.Execute,该方法用于执行委托,实现如下:

          

     /// summary>
        /// 核心函数,执行Handler的方法
        /// /summary>
        /// param name="page">页面对象/param>
        /// param name="context">请求上下文/param>
        /// param name="cacheMethodInfo">缓存方法原数据/param>
        internal static void Execute(Page page, HttpContext context, CacheMethodInfo methodInfo)
        {
          if (page == null)
          {
            throw new ArgumentNullException("page");
          }
          try
          {
            if (methodInfo != null)
            {
              HttpRequest request = context.Request;
              object[] parameters = GetParametersFromRequest(request, methodInfo.Parameters);
              object data = methodInfo.Func(page, parameters);
              int serverCache = methodInfo.AjaxMethodAttribute.ServerCache;
              if (serverCache > 0)
              {
                CacheHelper.Insert(context, methodInfo.AjaxMethodAttribute.ServerCache, data);
              }
              EndCurrentRequest(context, data, methodInfo.AjaxMethodAttribute.OutputCache);
            }
            else
            {
              EndCurrentRequest(context, "找不到合适的Ajax方法!");
            }
          }
          catch (FormatException)
          {
            EndCurrentRequest(context, "调用方法匹配到无效的参数!");
          }
          catch (InvalidCastException)
          {
            EndCurrentRequest(context, "参数转换出错!");
          }
          catch (System.Threading.ThreadAbortException)
          {
            //do nothing
          }
          catch (Exception ex)
          {
            EndCurrentRequest(context, ex.Message);
          }
        }
    

      CacheMethodInfo我们已经获得了,现在只要获得参数我们就可以执行方法。

      GetParameterFromRequest用于从请求获取object[]参数数组。根据上面所说的,如果参数是一个简单类型,那么直接进行转换;如果是实例对象,那么我们先要创建new一个实例对象,然后为其字段或属性赋值。实现如下:

          

     /// summary>
        /// 从请求获取参参数
        /// /summary>
        /// param name="request">HttpRequest/param>
        ///param name="parameters">参数信息/param>
        /// returns>参数数组/returns>
        private static object[] GetParametersFromRequest(HttpRequest request, ParameterInfo[] parameters)
        {
          if (parameters.IsNullOrEmpty())
          {
            return null;
          }
          int length = parameters.Length;
          object[] realParameters = new object[length];
          for (int i = 0; i  length; i++)
          {
            ParameterInfo pi = parameters[i];
            Type piType = pi.ParameterType.GetRealType();
            object value = null;
            if (piType.IsValueType())
            {
              //值类型
              value = ModelUtil.GetValue(request, pi.Name, piType);
              value = value ?? Activator.CreateInstance(piType);
            }
            else if (piType.IsClass)
            {
              //引用类型
              object model = ModelUtil.CreateModel(piType);
              ModelUtil.FillModelByRequest(request, pi.Name, piType, model);
              value = model;
            }
            else
            {
              throw new NotSupportedException(pi.Name + " 参数不被支持");
            }
            realParameters[i] = value;
          }
          return realParameters;
        }
    

      ModelUtil会从Http Request获取参数,并进行类型转换处理:

      

     internal static class ModelUtil
      {
        /// summary>
        /// 缓存构造函数
        /// /summary>
        private static Hashtable constructorTable = Hashtable.Synchronized(new Hashtable());
     
        /// summary>
        /// 根据名称从HttpRequest获取值
        /// /summary>
        /// param name="request">HttpRequest/param>
        /// param name="name">键名称/param>
        /// param name="type">参数类型/param>
        /// returns>/returns>
        public static object GetValue(HttpRequest request, string name, Type type)
        {
          string[] values = null;
          if (string.Compare(request.RequestType, "POST", true) == 0)
          {
            values = request.Form.GetValues(name);
          }
          else
          {
            values = request.QueryString.GetValues(name);
          }
          if (values.IsNullOrEmpty())
          {
            return null;
          }
          string data = values.Length == 1 ? values[0] : string.Join(",", values);
          return Convert.ChangeType(data, type);
        }
    
        /// summary>
        /// 创建实例对象
        /// /summary>
        /// param name="type">实例类型/param>
        /// returns>/returns>
        public static object CreateModel(Type type)
        {
          if (type == null)
          {
            throw new ArgumentNullException("type");
          }
          Funcobject> func = constructorTable[type.AssemblyQualifiedName] as Funcobject>;
          if (func == null)
          {  
            func = ReflectionUtil.GetConstructorDelegate(type);
            constructorTable[type.AssemblyQualifiedName] = func;
          }
          if (func != null)
          {
            return func();
          }
          return null;
        }
    
        /// summary>
        /// 填充模型
        /// /summary>
        /// param name="request">HttpRequest/param>
        /// param name="name">键名称/param>
        /// param name="prefix">参数类型/param>
        /// parparam name="model">实例对象/parparam>
        public static void FillModelByRequest(HttpRequest request, string name, Type type, object model)
        {
          if (model == null)
          {
            return;
          }
          IEnumerableDataMember> members = DataMemberManager.GetDataMember(type);
          if (members.IsNullOrEmpty())
          {
            return;
          }
          object value = null;
          foreach (DataMember member in members)
          {
            value = GetValue(request, string.Format("{0}.{1}", name, member.Name), member.MemberType);
            value = value ?? GetValue(request, member.Name, member.MemberType);
            member.SetValue(model, value);
          }
        }
      }
    
    

      如果是引用类型,需要通过构造函数创建对象,像前面用于,这里我们也用Expression来构建一个Funcobject>类型的委托来优化,它调用了ReflectionUtil.GetConstructorDelegate方法。实现如下:

      /// summary>
        /// 获取构造函数委托
        /// /summary>
        /// param name="type">实例类型/param>
        /// returns>/returns>
        public static Funcobject> GetConstructorDelegate(Type type)
        {
          if (type == null)
          {
            throw new ArgumentNullException("type");
          }
          ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
          if (ci == null)
          {
            throw new MissingMemberException("类型必须有一个无参public构造函数!");
          }
          NewExpression newExp = Expression.New(type);
          ExpressionFuncobject>> lambda = Expression.LambdaFuncobject>>(newExp);
          return lambda.Compile();
        }
    

      最后再输出结果时,如果是Get请求,并且需要缓存,我们还需要设置一下Response.Cache。如下:

        private static void EndRequest(HttpContext context, object data, int outPutCache, ContentType contentType)
        {
          HttpResponse response = context.Response;
          if (outPutCache != 0)
          {
            if (string.Compare(context.Request.HttpMethod, "GET", true) == 0)
            {
              if (outPutCache > 0)
              {
                response.Cache.SetCacheability(HttpCacheability.Public);
                response.Cache.SetMaxAge(new TimeSpan(0, 0, outPutCache));
                response.Cache.SetExpires(DateTime.Now.AddSeconds(outPutCache));
              }
              else
              {
                response.Cache.SetCacheability(HttpCacheability.NoCache);
                response.Cache.SetNoStore();
              }
            }
          }
          response.ContentType = GetContentType(contentType);
          response.ContentEncoding = System.Text.Encoding.UTF8;
          if (data != null)
          {
            response.Write(data);
          }
          response.End();
        }
    

    总结
      现在不管我们前台用什么脚本库,只要按照约定就可以调用标记方法。上面已经介绍了组件的核心部分,您也可以按照自己的想法进行扩展,也欢迎共同学习交流。

    您可能感兴趣的文章:
    • Ajax核心XMLHTTP组件资料
    • 编写轻量ajax组件02--浅析AjaxPro
    • 编写轻量ajax组件01-与webform平台上的各种实现方式比较
    上一篇:AJAX XMLHttpRequest对象详解
    下一篇:一个简单Ajax类库及使用方法实例分析
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯

    时间:9:00-21:00 (节假日不休)

    地址:江苏信息产业基地11号楼四层

    《增值电信业务经营许可证》 苏B2-20120278

    编写轻量ajax组件第三篇实现 编写,轻量,ajax,组件,第三,