軽量の Ajax コンポーネントを作成する 3 番目の実装

亚连
リリース: 2018-05-24 10:38:23
オリジナル
1438 人が閲覧しました

この記事では、主に軽量の Ajax コンポーネントを作成する 3 番目の実装を詳細に紹介します。これには特定の参考値があります。興味のある方は参照してください。

前の紹介を通じて、中核となるのはリフレクションです。リクエストからパラメータを取得し、指定されたメソッドを実行するプロセスです。実際、これは ASP.NET MVC フレームワークの中心的な考え方に非常に似ており、URL を解析し、そこからコントローラーとアクション名を取得し、コントローラー オブジェクトをアクティブ化し、リクエストからアクション パラメーターを取得します。アクションを実行します。 Web フォーム プラットフォームでは、.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 mark属性

Mark属性はリフレクションに使用されます。ここで必要な関数をいくつか定義します。期待したいのは:

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

前の記事によると、特定のハンドラーは主に非同期と非同期の 2 つのカテゴリに分けられ、セッションのステータスは 3 です。サポートされていない、読み取りのみサポート (IReadOnlySessionState インターフェイスの実装)、読み取りと書き込みのサポート (IRequiresSessionState インターフェイスの実装) の 3 つのタイプです。 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 は IHand を実装しますlerF 特定のハンドラーを生成するために、actory インターフェースが使用されます。リクエストに基づいて web.config に登録する必要があります。 AjaxHandlerFactory の GetHandler は、リクエストをインターセプトするための最初のステップです。リクエスト ヘッダーの AjaxFlag:XHR を使用して、処理する必要があるかどうかを判断し、必要な場合はハンドラーを作成します。それ以外の場合は、通常の方法で処理します。このメソッドは .aspx.cs で記述されているため、リクエストはページ (Page、IHttpHandler を実装する) タイプである .aspx サフィックスを持ちます。ページは PageHandlerFactory を通じて作成されます。これは、IHandlerFactory インターフェイスも実装します。つまり、次の目的で使用されます。ハンドラーを作成します。したがって、PageHandlerFactory を使用して IHttpHandler を作成する必要があります。ただし、PageHandlerFactory のコンストラクターは保護された内部型であるため、新しいコンストラクターを直接作成することはできないため、CommonPageHandlerFactory を通じて継承する必要があります。

PageHandlerFactoryでPageを取得した後、メソッド名と組み合わせてリフレクションにより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 にシリアル化されますが、ここでは実装されていません)。デリゲートは 2 つのパラメーターを受け取ります。最初のパラメーターはメソッドが属するオブジェクトであり、2 番目のパラメーターは 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委托,为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 コンポーネントを作成する 3 番目の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!