0

0

编写轻量级ajax组件(图文详解)

php中世界最好的语言

php中世界最好的语言

发布时间:2018-04-04 17:07:57

|

1364人浏览过

|

来源于php中文网

原创

这次给大家带来编写轻量级ajax组件(图文详解),编写轻量级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)
    {
      //执行方法(下面详细介绍)
      Action<Page, HttpContext, CacheMethodInfo> action = new Action<Page, HttpContext, CacheMethodInfo>(Executor.Execute);
      IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action);
      return result;
    }
    public void EndProcessRequest(IAsyncResult result)
    {
      Action<Page, HttpContext, CacheMethodInfo> action = result.AsyncState as Action<Page, 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去执行,这样的效率比较低。这里我缓存的是方法的委托,该委托的签名为:Func<object, object[], object>,该委托的返回值为object类型,表示可以返回任意的类型(我们可以在组件内部进行处理,例如如果是引用类型(非string),就将其序列化为json,但这里并没有实现)。该委托接收两个参数,第一个参数是方法所属的对象,如果是静态方法就是null;第二个参数是方法的参数,定义为object[]表示可以接收任意类型的参数。通过委托执行方法,与直接调用方法的效率差别就不是很大(对委托不熟悉的朋友可以参见:委托)。CacheMethodInfo的定义如下:

 /// <summary>
  /// 缓存方法信息  
  /// </summary>
  sealed class CacheMethodInfo
  {
    /// <summary>
    /// 方法名称
    /// </summary>
    public string MethodName { get; set; }
    /// <summary>
    /// 方法委托
    /// </summary>
    public Func<object, 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
      Dictionary<string, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary<string, CacheMethodInfo>;
      if (dic == null)
      {
        dic = new Dictionary<string, CacheMethodInfo>();
        //遍历页面的所有MethodInfo
        IEnumerable<CacheMethodInfo> 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编译成Func<object,object[],object>委托,为Type编译一个Func<object>委托,用于创建实例对象。

  通过Expression优化反射

  Expression(表达式树)允许我们将代码逻辑以表达式的形式存储在树状结构里,然后在运行时去动态解析,实现动态编辑和执行代码。熟悉ORM框架的朋友对Expression肯定很熟悉,因为大部分方法都有一个Expression<TDelegate>类型的参数。访问关系型数据库的本质还是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;
      */
      /*
      通过委托调用
      Func<int, int, int> func = new Func<int, 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.Lambda<Func<int, int, int>>(unaryExpression, parametersExpression);
      //将委托编译成可执行代码
      Func<int,int,int> func = lambdaExpression.Compile() as Func<int,int,int>;
      Console.WriteLine(func(1, 2));

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

 public static Func<object, 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();
      //参数表达式集合
      List<Expression> paramExpList = new List<Expression>();
      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))
      {
        Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceExp, paramExp);
        Action<object, object[]> action = lambda.Compile();
        return (instance, parameters) =>
        {
          action(instance, parameters);
          return null;
        };
      }
      else
      {
        UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
        Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceExp, paramExp);
        return lambda.Compile();
      }
    }

  具体代码都有注释解释,最终我们获得了一个Func<object,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 List<DataMember> GetDataMember(Type type)
    {
      if (type == null)
      {
        throw new ArgumentNullException("type");
      }
      IEnumerable<PropertyMember> propertyMembers = from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                         select new PropertyMember(property);
      IEnumerable<FieldMember> fieldMembers = from field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                       select new FieldMember(field);
      List<DataMember> members = new List<DataMember>();
      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");
      }
      Func<object> func = constructorTable[type.AssemblyQualifiedName] as Func<object>;
      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;
      }
      IEnumerable<DataMember> 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来构建一个Func<object>类型的委托来优化,它调用了ReflectionUtil.GetConstructorDelegate方法。实现如下:

  /// <summary>
    /// 获取构造函数委托
    /// </summary>
    /// <param name="type">实例类型</param>
    /// <returns></returns>
    public static Func<object> 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);
      Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(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();
    }

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

Cutout.Pro抠图
Cutout.Pro抠图

AI批量抠图去背景

下载

Ajax请求async有哪些方式?应该如何使用

FileUpload实现单个文件的上传

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

1135

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

340

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

381

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2235

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

380

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

1723

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

586

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

441

2024.04.29

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

69

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号