경량 Ajax 구성요소 작성의 세 번째 구현

亚连
풀어 주다: 2018-05-24 10:38:23
원래의
1438명이 탐색했습니다.

이 글에서는 경량 Ajax 구성요소 작성의 세 번째 구현을 주로 소개합니다. 관심 있는 친구들이 참고할 수 있습니다.

이전 소개를 통해 페이지 객체를 실행하는 방법을 알았으며 핵심은 리플렉션입니다. 요청에서 매개변수를 얻고 지정된 메소드를 실행하는 프로세스입니다. 실제로 이는 ASP.NET MVC 프레임워크의 핵심 아이디어와 매우 유사합니다. URL을 구문 분석하고 여기에서 컨트롤러와 작업 이름을 가져온 다음 컨트롤러 개체를 활성화하고 요청에서 작업 매개변수를 가져옵니다. 작업을 실행합니다. 웹 양식 플랫폼에서는 .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 헤더의 MethodName은 실행하려는 메소드의 이름을 나타냅니다.

AjaxMethodAttribute 마크 속성

  마크 속성은 반사에 사용됩니다. 여기서는 필요한 몇 가지 기능을 정의합니다. 우리는 다음을 희망합니다:

  1. 세션 상태 구성 가능

  2. 비동기 핸들러 지원

 3. 캐시 가져오기 지원

 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

이전 기사에 따르면 특정 핸들러는 주로 비동기식 및 비동기식의 두 가지 범주로 나뉘며 이 두 범주에서 세션 상태는 3입니다. 지원되지 않는 세 가지 유형, 읽기만 지원(IReadOnlySessionState 인터페이스 구현) 및 읽기 및 쓰기 지원(IRequiresSessionState 인터페이스 구현). IReadOnlySessionState 및 IRequiresSessionState는 단지 표시 인터페이스일 뿐입니다(메서드가 없으면 표시 특성을 사용하여 구현하는 것이 더 합리적입니다). 비동기 처리기는 IHttpAsyncHandler 인터페이스를 구현해야 하며, 이 인터페이스는 IHttpHandler를 구현합니다. Handler의 ProcessRequest 메서드(또는 BeginProcessRequest)는 메서드를 실행하려는 곳입니다. 정의는 다음과 같습니다.

비동기 상태의 핸들러:

 //不支持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)
    {
    }
  }
로그인 후 복사

비동기 상태의 핸들러:

 //不支持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는 IHandlerF를 구현합니다. 특정 핸들러를 생성하는 데 사용됩니다. 요청에 따라 web.config에 등록해야 합니다. AjaxHandlerFactory의 GetHandler는 요청을 가로채는 첫 번째 단계입니다. 요청 헤더에 있는 AjaxFlag:XHR을 사용하여 처리해야 하는지 여부를 결정합니다. 그렇다면 핸들러를 생성하고, 그렇지 않으면 일반적인 방법으로 진행합니다. 메서드가 .aspx.cs에 작성되었으므로 요청은 페이지(페이지, IHttpHandler 구현) 유형인 .aspx 접미사로 되어 있습니다. 페이지는 IHandlerFactory 인터페이스도 구현하는 PageHandlerFactory를 통해 생성됩니다. 핸들러를 생성합니다. 따라서 IHttpHandler를 생성하려면 PageHandlerFactory를 사용해야 합니다. 그러나 PageHandlerFactory의 생성자는 보호된 내부 유형이므로 직접 생성할 수 없으므로 CommonPageHandlerFactory를 통해 상속해야 합니다.

 PageHandlerFactory를 통해 페이지를 얻은 후 메소드 이름과 결합하면 리플렉션을 통해 AjaxMethodAttribute 마크 속성을 얻을 수 있습니다. 그런 다음 관련 속성을 기반으로 특정 처리기를 생성합니다. 구체적인 코드는 다음과 같습니다.

 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입니다. 대리자의 반환 값은 개체 형식이므로 모든 형식이 반환될 수 있습니다. 예를 들어 참조 유형(문자열이 아님)인 경우 json으로 직렬화되지만 여기서는 구현되지 않습니다. 대리자는 두 개의 매개변수를 받습니다. 첫 번째 매개변수는 메서드가 속한 개체입니다. 정적 메서드인 경우 두 번째 매개변수는 개체[]로 정의됩니다. 모든 유형의 매개변수를 수신할 수 있습니다. 실행 메소드를 위임함으로써 메소드를 직접 호출하는 것의 효율성 차이는 그리 크지 않습니다. (위임에 익숙하지 않은 친구들은 위임을 참고하세요.) 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委托,为Type编译一个Func委托,用于创建实例对象。

  通过Expression优化反射

  Expression(表达式树)允许我们将代码逻辑以表达式的形式存储在树状结构里,然后在运行时去动态解析,实现动态编辑和执行代码。熟悉ORM框架的朋友对Expression肯定很熟悉,因为大部分方法都有一个Expression类型的参数。访问关系型数据库的本质还是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类型的委托,它会作为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类型的委托来优化,它调用了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();
    }
로그인 후 복사

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

ajax返回object Object的快速解决方法

jQuery中ajax的4种常用请求方式介绍

使用原生ajax处理json字符串的方法

위 내용은 경량 Ajax 구성요소 작성의 세 번째 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!